一道很好的锻炼思惟难度的题,若是您能在考场上直接想出来的话,提升组450分以上就没问题了吧。(别像做者同样看了好几篇题解才勉强会)node
先提取出题目大意:给定一个长度n<=40000的01串,其中1的个数<=8,有m种操做,每次操做都是把一个该操做对应长度的区间取反,或者说异或上1,求使整个串变为只有0的串的最小操做次数。 ios
首先对于一次操做,确定不能暴力地一个个去取反吧。优化区间操做,要么用数据结构,要么用前缀和或差分。其实这里能够用差分来优化:创建新的下标最大为n+一、下标正常从1开始的d数组,d[i]=a[i]^a[i-1],每次区间修改时,设修改的区间的左端点为l,右端点为r,只要让a[l]和a[r+1]分别异或上1就好了。git
解释一下:这里的差分中的“差”已经不能简单理解为减法作差了,而是逻辑上的“差距”。差分维护的是相邻元素间的逻辑关系,从而使能从初始状态(a[0])经过差分数组表达的逻辑关系推出某个位置上a的值(从形式上看就是求前缀)。对于异或来讲,正好知足这样的性质:咱们读入串时从a[1]开始读入,那a[0]没管它的话就会是0,那么发现从它开始向后与d数组作前缀异或时,设当前作到第i个位置了(即当前值=a[0]^d[1]^d[2]^……^d[i]),则当前值就是a[i]的值,一样对于差分优化的区间操做来讲,对 左端点 和 右端点+1 处取反后,在求一遍前缀异或,发现对于那个要修改的区间,真的就取反了。(能够这么考虑:对于区间中的位置来讲,修改后再求完前缀后,每位都比修改前的这位多异或了1,故取反;对于区间后面的位置,修改后再求完前缀后,每位都比修改前的这位多异或了2个1,就不会改变,整体上看,这个区间就被取反了。这要依赖于异或这个运算可交换且有单位元(么元)、且1有对于异或的逆元(其实全部数都有)(逆元可不仅限于取模哟))。由于要能实现右端点等于n的区间修改,因此d数组的最大下标为n+1,同时也是为下文1的个数为偶数的结论作铺垫。数组
那么问题就变为:给定一个长度n<=40001的01串,其中1的个数<=16,有m种操做,每次操做都是把下标差该操做对应长度的两个数取反,求使整个串变为只有0的串的最小操做次数。数据结构
解释一下:考虑将原串的1全都拿出来后一个个加入到一个全是0的串u里,造成一个与原串彻底同样的串,看看u串对应的差分数组的变化,发现每次加入一个1,差分数组要么新增2个1(加入u串的1在u串中左右没有相邻的1),要么有一个1日后或前移一个位置(加入u串的1在u串中的左边或右边中只有一边有相邻的1),要么减小2个1(加入u串的1在u串中的左边和右边都相邻的1),由于要从u串中加最多8个1,显然对应的差分数组最多就16个1,同时差分数组中1的个数必定为偶数。优化
显然对于每次操做取反的两个数中必定有一个1,否则此次操做不但没用,还多出了2个1,浪费次数还增多任务。考虑每次取反的两个数:spa
一、有1个1:那么结果是原来1的位置如今变成了0,原来0的位置如今变成了1。形象化地,1从原来的位置移动到了另外一个位置code
二、有2个1:那么这两个1都会变为0,形象化地一个1移动到了有1的位置,两个1碰到一块儿就消失了。blog
因而问题又被形象化地转化为:给定一个长度n<=40001的01串,其中1的个数<=16,有m种移动长度,每次移动都是把1个1移动这个移动相应的移动长度,若2个1碰到一块儿(在同一个位置)就会消失,求使整个串的全部1消失的最小移动次数。get
若是咱们知道了这些1两两碰到一块儿消失的最小移动次数,跑个状压DP就能够喽。而这些1两两碰到一块儿消失的最小移动次数,正能够对每一个1跑一遍BFS求得(把串的每一位当作一个点,向这个点能一次移动到的全部点都连一条长度为1的边),时间复杂度O(kmn),彻底能够接受。至于状压DP,推荐写O(k*(2的2*K次方))的DP,若是写O((k的平方)*(2的2*K次方))的DP,虽然能过这个题,可是很容易到考场上被卡。关于O(k*(2的2*K次方)),咱们能够从当前状态向之后状态转移,当前状态第一个没有被消去的1早晚要被消去,不如先消去,转移到包括这个1的状态,容易发现每个消去全部1的状态,均可以经过这样的策略实现出来,因此这样是正确的。
最后看看代码吧:
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<queue> 5 6 using namespace std; 7 8 const int N=40005,M=64,K=8; 9 10 int n,k,m,x,a[N],d[N],caolen[M+2],d1[K<<2],cntd1,lst[N],nxt[N*M<<1],to[N*M<<1],cnt; 11 int wei[N],vis[N]; 12 13 long long dp[1<<K*2],dis[K<<3][K<<3]; 14 15 char ch; 16 17 inline int read() 18 { 19 x=0; 20 ch=getchar(); 21 while(!isdigit(ch)) ch=getchar(); 22 while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar(); 23 return x; 24 } 25 26 inline void addedge(int u,int v) 27 { 28 nxt[++cnt]=lst[u]; 29 lst[u]=cnt; 30 to[cnt]=v; 31 } 32 33 struct node{ 34 int w,dlen; 35 }head; 36 37 queue<node>q,ling; 38 39 inline void bfs(int w,int ord)//变量含义:这个1在原串中的位置,这个1在d1(将d中的1又单独存了一下)中的位置 40 { 41 memset(vis,0,sizeof vis); 42 q=ling; 43 vis[w]=1; 44 int cntin=1,t; 45 q.push((node){w,0}); 46 while(!q.empty()) 47 { 48 head=q.front(); 49 q.pop(); 50 for(int e=lst[head.w];e;e=nxt[e]) 51 if(!vis[t=to[e]]) 52 { 53 vis[t]=1; 54 cntin++; 55 if(d[t]) 56 dis[ord][wei[t]]=head.dlen+1; 57 if(cntin==n) 58 return; 59 q.push((node){t,head.dlen+1}); 60 } 61 } 62 } 63 64 inline void init() 65 { 66 n=read(),k=read(),m=read(); 67 for(int i=1;i<=k;++i) 68 a[read()]=1; 69 for(int i=1;i<=m;++i) 70 caolen[i]=read(); 71 for(int i=1;i<=n+1;++i) 72 { 73 d[i]=a[i]^a[i-1]; 74 if(d[i]) 75 { 76 d1[++cntd1]=i; 77 wei[i]=cntd1; 78 } 79 80 } 81 for(int i=1;i<=n+1;++i) 82 for(int j=1;j<=m;++j) 83 { 84 if(i+caolen[j]<=n+1) 85 addedge(i,i+caolen[j]); 86 if(i-caolen[j]>0) 87 addedge(i,i-caolen[j]); 88 } 89 memset(dis,0x3f,sizeof dis); 90 for(int i=1;i<=cntd1;i++) 91 bfs(d1[i],i); 92 } 93 94 int main() 95 { 96 init(); 97 int lim=(1<<cntd1)-1; 98 memset(dp,0x3f,sizeof dp); 99 dp[0]=0;//下标为1的消去状态(下标的二进制第i位为1:d1中第i个1已被消去) 100 for(int i=0;i<=lim;++i) 101 { 102 int j=0; 103 while(i&(1<<j)) 104 j++; 105 for(int t=j+1;t<cntd1;t++) 106 { 107 if(!(i&(1<<t))) 108 dp[i|(1<<j)|(1<<t)]=min(dp[i|(1<<j)|(1<<t)],dp[i]+dis[j+1][t+1]); 109 } 110 } 111 printf("%lld",dp[lim]); 112 return 0; 113 }