這一次我沒有參加,聽別人說是兩個DP,然后我還是想了好久。
第一題:有n段字符串,每串中的字符都是非遞減的,現可以將它們拼接,求最長的非遞減序列。其中 $1 \leq n \leq 10^6$,字符串的總長度不超過1e6且都由小寫字母組成。
分析:既然是DP,如果按前i個考慮,必定要排序,1e6肯定超時,所以要從另一個角度定義狀態,觀察到全都是小寫字母,非遞減,所以定義dp[i][j]為以第i個字母開始、第j個字母結束的最長非遞減序列。
#include<bits/stdc++.h> using namespace std; const int maxn = 1e6 + 10; int dp[26][26], n; char str[maxn]; void update() { int e1 = str[0] - 'a'; int e2 = str[strlen(str)-1] - 'a'; int len = strlen(str); for(int i = 0;i <= e1;i++) for(int j = 25;j >= e2;j--) { if(e1 == e2) // 插入 dp[i][j] = dp[i][j] + len; else // 拼接 dp[i][j] = max(dp[i][j], dp[i][e1] + len + dp[e2][j]); } } int main() { scanf("%d", &n); for(int i = 0;i < n;i++) { scanf("%s", str); update(); } printf("%d\n", dp[0][25]); }
第二題:有一副撲克牌,其中A, 2, 3, ..., 10各4張,A代表1。現在可以按一下方式打出牌:
- 單牌:一張牌。例如3
- 對子:數字相同的兩張牌。例如77
- 順子:數字連續的五張牌。例如A2345
輸入10個整數,表示每張牌的個數。輸出打光手中所有牌需要的最少次數。
例如1 1 1 2 2 2 2 2 1 1,最少3次。
分析:最開始覺得10維DP有點誇張,就想着用搜索+剪枝,
#include<bits/stdc++.h> using namespace std; const int INF = 0x3f3f3f3f; int a[10]; int min_times = INF; void dfs(int times) { //for(int i = 0;i < 10;i++) printf("%d ", a[i]); //printf("\n"); // 遞歸出口 bool flag = true; for(int i = 0;i < 10;i++) if(a[i]) { flag = false; break; } if(flag) { if(times < min_times) min_times = times; return; } if(times+1 >= min_times) return; // 剪枝 // 優先出順子 for(int i = 0;i < 6;i++) { if(a[i] && a[i+1] && a[i+2] && a[i+3] && a[i+4]) { for(int j = 0;j < 5;j++) a[i+j]--; dfs(times+1); for(int j = 0;j < 5;j++) a[i+j]++; } } // 再考慮出對子 for(int i = 0;i < 10;i++) { if(a[i] >= 2) { a[i] -= 2; dfs(times+1); a[i] += 2; } } // 出一張牌 for(int i = 0;i < 10;i++) { if(a[i] >= 1) { a[i]--; dfs(times+1); a[i]++; } } } int main() { for(int i = 0;i < 10;i++) scanf("%d", &a[i]); dfs(0); printf("%d\n", min_times); return 0; }
我試了大點的次數會超時,於是改成記憶化搜索,快了許多(果真10維DP。。。)
#include<bits/stdc++.h> using namespace std; const int INF = 0x3f3f3f3f; int a[10]; int min_times = INF; int d[5][5][5][5][5][5][5][5][5][5]; int dp() { //for(int i = 0;i < 10;i++) printf("%d ", a[i]); //printf("\n"); int& res = d[a[0]][a[1]][a[2]][a[3]][a[4]][a[5]][a[6]][a[7]][a[8]][a[9]]; if(res != 0) return res; res = INF; bool flag = true; for(int i = 0;i < 10;i++) // 判斷是否為全0 if(a[i]) { flag = false; break; } if(flag) { return res = 0; } // 優先出順子 for(int i = 0;i < 6;i++) { if(a[i] && a[i+1] && a[i+2] && a[i+3] && a[i+4]) { for(int j = 0;j < 5;j++) a[i+j]--; int tmp = dp()+1; if(tmp < res) res = tmp; for(int j = 0;j < 5;j++) a[i+j]++; } } // 再考慮出對子 for(int i = 0;i < 10;i++) { if(a[i] >= 2) { a[i] -= 2; int tmp = dp()+1; if(tmp < res) res = tmp; a[i] += 2; } } // 出一張牌 for(int i = 0;i < 10;i++) { if(a[i] >= 1) { a[i]--; int tmp = dp()+1; if(tmp < res) res = tmp; a[i]++; } } return res; } int main() { for(int i = 0;i < 10;i++) scanf("%d", &a[i]); int ans = dp(); printf("%d\n", ans); return 0; }
時間復雜度:狀態數O(5^10) * 每個狀態的轉移次數O(10+6+10) = 2.5e8,沒問題。
參考鏈接:https://blog.csdn.net/qq_28597451/article/details/105005092