这一次我没有参加,听别人说是两个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 ", 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(" "); // 递归出口 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 ", 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(" "); 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 ", ans); return 0; }
时间复杂度:状态数O(5^10) * 每个状态的转移次数O(10+6+10) = 2.5e8,没问题。
参考链接:https://blog.csdn.net/qq_28597451/article/details/105005092