1. 程式人生 > 其它 >題解:星空

題解:星空

  首先序列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