1. 程式人生 > >[線性DP][codeforces-1110D.Jongmah]一道花裏胡哨的DP題

[線性DP][codeforces-1110D.Jongmah]一道花裏胡哨的DP題

表示 思路 using esp bit problem 因此 class 數組

題目來源: Codeforces - 1110D

題意:你有n張牌(1,2,3,...,m)你要盡可能多的打出[x,x+1,x+2] 或者[x,x,x]的牌型,問最多能打出多少種牌

思路:

1.三組[x,x+1,x+2]的效果等同於 [x,x,x],[x+1,x+1,x+1],[x+2,x+2,x+2],所以每種順子牌型最多打2次(如果多於2次,可以被少於3次的方案替代掉,因此忽略)

2.對於每一種牌,用途只有四種。[i-2,i-1,i], [i-1,i,i+1], [i,i+1,i+2], [i,i,i]

 我們可以枚舉每張牌在四種用途裏分別投入了多少

 由簡單的容斥原理,我們只需要枚舉三種順子牌型分別有x,y,z組,那麽[i,i,i]就有(c[i]-x-y-z)/3組

 於是我們就有了線性dp的思路

 如果建立dp[maxn][3][3][3],dp[i][x][y][z]表示在第i階段,第i張牌用來組成三種牌型分別有x,y,z組的最優結果,有dp[i][x][y][z] = max{dp[i-1][?][x][y]} + z + (c[i] - x - y - z) / 3, ans = max{dp[n][?][0][0])

 AC代碼:

 1 #include <bits/stdc++.h>
 2 
 3 using namespace std;
 4 const int maxn = 1e6 + 5;
 5 int n;
 6 int c[maxn];
7 int dp[maxn][3][3][3]; 8 int main(){ 9 //freopen("data.in","r",stdin); 10 ios::sync_with_stdio(false); 11 cin.tie(0); 12 int cc,t; 13 cin >> cc >> n; 14 for(int i = 1; i <= cc; i++){ 15 cin >> t; 16 c[t]++; 17 } 18 for(int i = 1; i <= n; i++){
19 for(int x = 0; x < 3; x++){ 20 for(int y = 0; y < 3; y++){ 21 for(int z = 0; z < 3; z++){ 22 if(x + y + z > c[i])continue; 23 for(int t = 0; t < 3; t++) 24 dp[i][x][y][z] = max(dp[i][x][y][z], dp[i-1][t][x][y] + z + (c[i]-x-y-z)/3); 25 } 26 } 27 } 28 } 29 int ans; 30 for(int t = 0; t < 3; t++){ 31 ans = max(ans, dp[n][t][0][0]); 32 } 33 cout << ans << endl; 34 return 0; 35 }

3.仔細觀察上面的代碼,我們發現在狀態轉移過程中,兩個方程中都有[x][y],狀態之間的聯系太過緊密,可以嘗試優化一下

 如果建立dp[maxn][3][3],少了一個維度,dp[i][x][y] 表示有x組[i-1,i,i+1],y組[i,i+1,i+2]時的最優結果,有dp[i][x][y] = max{dp[i-1][?][x] + y + (c[i] - ? - x - y)/3}

 AC代碼:

 1 #include <bits/stdc++.h>
 2 
 3 using namespace std;
 4 
 5 const int maxn = 1e6 + 5;
 6 int n;
 7 int c[maxn];
 8 int dp[maxn][3][3];
 9 int main(){
10     //freopen("data.in","r",stdin);
11     ios::sync_with_stdio(false);
12     cin.tie(0);
13     int cc,t;
14     cin >> cc >> n;
15     for(int i = 1; i <= cc; i++){
16         cin >> t;
17         c[t]++;
18     }
19     for(int i = 1; i <= n; i++){
20         for(int x = 0; x < 3; x++){
21             for(int y = 0; y < 3; y++){
22                 for(int z = 0; z < 3; z++){
23                     if(x + y + z > c[i])continue;
24                     dp[i][y][z] = max(dp[i][y][z], dp[i-1][x][y] + z + (c[i]-x-y-z)/3);
25                 }
26             }
27         }
28     }
29     int ans = dp[n][0][0];
30     cout << ans << endl;
31     return 0;
32 }

4.由於每一階段的決策只與上一階段有關,可以使用滾動數組進行進一步優化。

[線性DP][codeforces-1110D.Jongmah]一道花裏胡哨的DP題