1. 程式人生 > >題解——[NOI2009]管道取珠 DP + 遞推

題解——[NOI2009]管道取珠 DP + 遞推

通過 include for 使用 可能 update freopen 滾動 我們

~~~題面~~~

思路:

非常神的一道題,,,,

主要難點在思路的轉化,

不能看見要求sigam(a[i]^2)就想著求a[i],

我們可以對其進行某種意義上的拆分,即a[i]實際上可以代表什麽?

假設我們現在有兩種取出某一數列的方法,分別為X,Y。(X,Y可以相同)

那麽這樣的二元組有多少個呢?

a[i]^2個。

因為X的取法有a[i]種,Y的取法也是a[i]種,所以二元組個數實際上就是a[i]^2.

那麽這樣一轉化有什麽好處?

方便DP

因為這樣的話就不在需要知道具體的a[i]了,因為二元組的個數是可以拆開來算的,

所以就可以考慮遞推/DP了。

我們用f[i][j][k][l]表示第一種決策X在上面取了i個,下面取了j個,第二種決策Y在上面取了k個,在下面取了l個且產生序列相同的二元組個數

顯然一開始的f[0][0][0][0]要設為1,

然後註意到我們應該要同步取,不然個數都不同,序列也不可能相同了,

因此我們數組中就可只存3維了,因為第4維可以通過i + j - k得到。

那麽應該如何轉移呢?

假設現在我們有f[i][j][k],顯然要使下一次取出來的序列相同,只要讓下一次取的珠子一樣就可以了,因為其他部分現在已經一樣了,

因此我們就可以分別枚舉X取上面,X取下面,Y取上面,Y取下面,然後相互搭配,一共4種情況,如果滿足下一次取的珠子一樣就可以轉移,

因為n有500,所以空間還是承受不了,觀察到i的轉移只涉及到i和i+1,因此我們可以用滾動數組優化。

但是我們註意到用f[i][j][k]向f[i+1][j][k+1]這類的轉移的話,如果不及時清空,因為使用的是滾動數組,轉移的時候又是+=,無法將原來的答案覆蓋,

那就會重復統計,因此我們不能向前轉移,我們應該要枚舉當前狀態,枚舉向當前狀態轉移的狀態,並且每次轉移之前要清空數組,這樣就可以了

註意將不合法的狀態減掉,luogu上這題貌似有點卡常,,,

work2是另一種狀態表示方法,,,我也不知道為什麽比work1快

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 #define R register int
 4 #define AC 510 
 5 #define mod 1024523
 6 /*轉化為有序數對的統計之後就可以不用在意具體數列到底是什麽樣的了,因為只要相同就可以了,
7 但是由於是同步取的,所以兩種決策取的次數是相同的,所以少掉一維是沒關系的, 8 因為剩下那維可以直接推出來,是沒用的*/ 9 int n,m,now; 10 int f[3][AC][AC]; 11 char U[AC],D[AC]; 12 void pre() 13 { 14 scanf("%d%d",&n, &m); 15 scanf("%s", U + 1); 16 scanf("%s", D + 1); 17 reverse(U+1,U+n+1); 18 reverse(D+1,D+m+1);//因為是從右邊開始取的,所以要反過來!!! 19 } 20 21 inline void update(int &a,int b) 22 { 23 a += b; 24 if(a > mod) a -= mod; 25 } 26 //本來是向前做貢獻,但是這樣貢獻會和前面的累加, 27 //所以需要在做的過程中初始化,但這樣就不太好掌控什麽時候初始化, 28 //因此改成從前面取貢獻,方便初始化,但是這樣的話前面的判斷就要麻煩點了 29 void work() 30 { 31 f[now][0][0] = 1;//一開始啥也沒取,,, 32 for(R i=0;i<=n;i++)//可以取0個啊 33 { 34 for(R j=0;j<=m;j++) 35 for(R k=0;k<=n;k++) 36 { 37 if(!i && !j) continue;//這個不能初始化掉了 38 if(i + j - k > m) continue;//因為i,j,k枚舉了,所以肯定合法,但最後一個要計算得到,不一定合法 39 f[now][j][k] = 0;//初始化,防止疊加 40 if(i && k && U[i] == U[k]) update(f[now][j][k],f[now^1][j][k-1]); 41 if(i && i + j - k && U[i] == D[i + j - k]) update(f[now][j][k],f[now^1][j][k]); 42 if(j && k && D[j] == U[k]) update(f[now][j][k],f[now][j-1][k-1]); 43 if(j && i + j - k && D[j] == D[i + j - k]) update(f[now][j][k],f[now][j-1][k]); 44 // printf("%d %d %d = %d\n",i,j,k,f[now][j][k]); 45 } 46 now ^= 1; 47 } 48 printf("%d\n",f[now^1][m][n]); 49 printf("time used ... %lf\n",(double)clock()/CLOCKS_PER_SEC); 50 } 51 52 void work1() 53 {//現在取了i個,第一種決策在第一行取了j個,第二種取了k個 54 f[1][0][0] = 1;//因為是滾了一維 55 int b = n + m; 56 for(R i = 1; i <= b; i++) 57 { 58 int lim = min(i, n); 59 for(R j = 0; j <= lim; j++) 60 { 61 for(R k = 0; k <= lim; k++) 62 { 63 int x = i - j, y = i - k;//x為第一種決策在第二行取的,y則是第二種決策 64 if(x > m || y > m) continue; 65 f[now][j][k] = 0; 66 if(j && U[j] == U[k]) update(f[now][j][k], f[now^1][j-1][k-1]); 67 if(j && U[j] == D[y]) update(f[now][j][k], f[now^1][j-1][k]); 68 if(x && D[x] == U[k]) update(f[now][j][k], f[now^1][j][k-1]); 69 if(x && D[x] == D[y]) update(f[now][j][k], f[now^1][j][k]); 70 // printf("%d %d %d = %d\n",i,j,k,f[now][j][k]); 71 } 72 } 73 now ^= 1; 74 } 75 printf("%d\n",f[now^1][n][n]); 76 // printf("time used ... %lf\n",(double)clock()/CLOCKS_PER_SEC); 77 } 78 79 int main() 80 { 81 //freopen("in.in","r",stdin); 82 pre(); 83 //work(); 84 work1(); 85 //fclose(stdin); 86 return 0; 87 }

題解——[NOI2009]管道取珠 DP + 遞推