題解:星空
首先序列A中存在若干個0與若干個1(假定0代表燈亮,1代表燈滅),目標是通過若干次操作使得序列所有元素均為0
每次操作可以從給定的m種長度中選擇一種,將序列中任意一段長度為選定長度的子序列取反,求最有情況下至少需要多少次操作
考慮分治問題,首先根據資料範圍,若在每次操作是均以O(len)複雜度對序列取反必然超時,也就是說首先需要優化取反操作
類似於T1入陣曲,區間取反也是對範圍資訊的查詢(操作),考慮對於範圍資訊進行維護的兩種預處理手段:字首和與差分,
發現利用差分我們可以在O(1)複雜度對區間進行取反,那麼問題主體就由原序列轉移到差分序列。
長度為n的序列中,存在不超過2k個1,求使得序列所有元素均為0的最小操作次數。問題元素很少,在解決完區間取反問題後
就要考慮每次操作的效果,很顯然每次操作我們可以選取區間上一定距離的兩點進行取反。考慮為了是的操作次數最少,每次只需
對兩個位置為1,0或1,1進行操作(因為對0,0進行操作無意義),若1,1進行操作則二者均消失,若1,0進行操作則二者互換
位置,發現問題進一步轉化,在可供選擇的距離下,如何經過最小操作次數使得所有1相互移動最終消失,因為只有有限種距離可
供選擇,故我們發現任意兩個1消失所需最小距離是一定的,通過bfs可以預處理得到。
最終問題呈現形式為,存在不超過2k個物品,已知任意2個物品消失代價,如何選擇使得所有物品消失總代價最小,很顯然可以
通過狀壓DP解決。
最終考慮一下幾個問題:
首先差分序列所有為0如何保證原序列全為0而非全為1(其實可以模擬,但思考原理更重要)。考慮差分序列對於本題的意義,其
代表原序列的不同性,一個被我們忽視的重要潛條件為:在進行差分時我們預設0號燈亮!這意味著從0號燈後若差分序列為1則代表
燈滅。此時在預設0號燈亮與差分意義的雙重製約下保證操作完成時原序列為0;其實也可以考慮為在保證最優情況下,由於主觀參與會
保證只對1進行操作,但是貌似並不嚴謹。
第二考慮時間複雜度優化問題,也是常見的最優化問題的等效手段。在最後進行DP是我們顯然可以k^2列舉所有情況取min,然而
時間複雜度並不保險,考慮問題僅需求出最終最優結果,我們並不需要保證每個狀態的最優性,因此我們只需要使得問題不斷向最終結果
拓展即可。另一種理解為,在狀態轉移時發現有很多冗餘情況,為了優化複雜度,我們在列舉時進列舉當前狀態對以後第一個造成影響的
狀態轉移即可,可以發現能列舉到所有情況,詳見程式碼(注意理解應用最優化問題優化部分的程式碼):
1 #include<bits/stdc++.h> //注意差分序列範圍為1-n+1或0-n,分別對應不同的修改方式 2 using namespace std; //為了便於DP採取0-n,修改方式為l-1,r 3 #define P pair //sta作用為虛擬建圖以及還原節點 4 #define Q queue 5 #define I int 6 #define LL long long 7 #define B bool 8 #define C char 9 #define RE register 10 #define L inline 11 I n,k,m,cnt,len[64]; 12 LL dis[16][40005],DP[1<<16]; 13 B state[40005]; 14 P<I,I> sta[16]; 15 Q<I>q; 16 L I read(){RE I x(0);RE C z(getchar());while(!isdigit(z))z=getchar();while(isdigit(z))x=x*10+(z^48),z=getchar();return x;} 17 signed main(){ 18 n = read(); k = read(); m = read(); 19 for(RE I i(1);i <= k; ++ i) state[read()] = 1; 20 for(RE I i(0);i <= n; ++ i) if(state[i] ^ state[i + 1]) sta[cnt] = make_pair(cnt,i),cnt++; 21 for(RE I i(0);i < m; ++ i) len[i] = read(); 22 for(RE I i(0);i < cnt; ++ i){ 23 I fir(sta[i].first),sec(sta[i].second); 24 for(RE I j(0);j <= n; ++ j) dis[fir][j] = INT_MAX; 25 q.push(sec); dis[fir][sec] = 0; 26 while(!q.empty()){ 27 I x(q.front()); q.pop(); 28 for(RE I i(0);i < m; ++ i){ 29 if(x - len[i] >= 0 && dis[fir][x - len[i]] > dis[fir][x] + 1) 30 dis[fir][x - len[i]] = dis[fir][x] + 1,q.push(x - len[i]); 31 if(x + len[i] <= n && dis[fir][x + len[i]] > dis[fir][x] + 1) 32 dis[fir][x + len[i]] = dis[fir][x] + 1,q.push(x + len[i]); 33 } 34 } 35 } 36 memset(DP,0x3f,sizeof(DP)); DP[0] = 0; 37 for(RE I i(0),x(0);i < 1 << cnt; ++ i,x = 0){ 38 while(i & 1 << x) x++; 39 for(RE I j(x + 1);j < cnt; ++ j) 40 if(!(i & 1 << j)) DP[i|1<<x|1<<j] = min(DP[i|1<<x|1<<j],DP[i] + dis[x][sta[j].second]); 41 } 42 printf("%lld",DP[(1<<cnt)-1]); 43 }View Code