洛谷P3943 星空——題解

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