一道很好的鍛鍊思惟難度的題,若是您能在考場上直接想出來的話,提升組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 }