dp问题
1.区间dp
P1063 [NOIP2006 提高组] 能量项链 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
对于环形问题,我们常常可以采用将n个元素复制成2n个元素,或者选择(i + 1) % n的形式
第一次遇到区间dp,写个题解总结一下
区间dp能解决的问题就是通过小区间更新大区间,最后得出指定区间的最优解。
想要用区间dp解决问题,首先要确定一个大问题能够剖分成几个相同较小问题,且小问题很容易组合成大问题,从而从解决小问题逐渐解决大问题,体现的其实是分治的思想,只不过是通过dp用递推的方式解决了。
本题应通过演算过程发现最终问题的解决可由两个相同规模较小的问题轻松地转化过来。(一般分治时只分成两个简化程序) 用f[l][r]表示以a[l]开头a[r]结尾的数串的最大和,如k为i,j之间任一节点,有f[l][r]=max(f[l][r],f[l][k]+f[k][r]+a[l]*a[k]*a[r]); 对l,r的定义自己一定要十分清晰,从而确定好循环的边界。
但也有问题随之而来,在更新时要将2*n个元素都更新,而不能只更新到前n个,否则访问到n+1~2n时会出错

1 #include <bits/stdc++.h> 2 using namespace std; 3 4 const int N = 410; 5 int f[N][N]; 6 int n,a[205]; 7 int main() 8 { 9 cin >> n; 10 for(int i=1;i<=n;i++) //***对环形问题的处理技巧*** 11 { 12 cin >> a[i]; 13 a[n+i]=a[i]; 14 } 15 16 //可以视为项链每3个为一个区间 17 for(int i = 2;i <= n + 1;i ++) 18 { 19 //更新区间起点 20 for(int l = 1;l + i - 1 <=2 * n;l ++) //如果采取了上述策略,一定要将2*n个点都更新 21 { 22 int r = l + i - 1; //区间末端 23 for(int k = l + 1 ;k <= l + i - 2;k ++)//区间中的断点,表示项链的第三个点 24 f[l][r]=max(f[l][r],f[l][k]+f[k][r] + a[l] * a[k] * a[r]); 25 } 26 } 27 28 int res = 0; 29 for (int i = 1;i <= n;i ++) res = max(res,f[i][n + i]); 30 cout << res; 31 return 0; 32 }
2.P1002 [NOIP2002 普及组] 过河卒 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
这道题dp状态方程不难想,而是要注意边界情况的处理,可以把所有坐标全都+2 防止越界,并用向量来判断有没有被马拦住,如果拦住,跳过即可

1 #include<iostream> 2 #include<cstring> 3 #include<cstdio> 4 #include<algorithm> 5 #define ll long long 6 using namespace std; 7 8 const int fx[] = {0, -2, -1, 1, 2, 2, 1, -1, -2}; 9 const int fy[] = {0, 1, 2, 2, 1, -1, -2, -2, -1}; 10 //马可以走到的位置 11 12 int n, m, a, b; 13 ll f[40][40]; 14 bool s[40][40]; //判断这个点有没有马拦住 15 int main(){ 16 scanf("%d%d%d%d", &n, &m, &a, &b); 17 n += 2; m += 2; a += 2; b += 2; 18 19 //坐标+2以防越界 20 f[2][1] = 1;//初始化 21 s[a][b] = 1;//标记马的位置 22 for(int i = 1; i <= 8; i++) s[a + fx[i]][b + fy[i]] = 1; 23 for(int i = 2; i <= n; i++){ 24 for(int j = 2; j <= m; j++){ 25 if(s[i][j]) continue; // 如果被马拦住就直接跳过 26 f[i][j] = f[i - 1][j] + f[i][j - 1]; 27 //状态转移方程 28 } 29 } 30 printf("%lld\n", f[n][m]); 31 return 0; 32 }
3.Problem - 1903C - Codeforces
这道题明显要让后面的值越来越大最好,因为后面的系数大,所以我们考虑后缀和,但是由于存在乘法,乘起来比较麻烦,所以我们可以用后缀和去
不断地累加,也能达到乘法的效果

1 #include<bits/stdc++.h> 2 using namespace std; 3 #define int long long 4 int t,n; 5 int a[100005],f[100005]; 6 7 signed main(){ 8 ios::sync_with_stdio(false);cin.tie(0),cout.tie(0); 9 10 cin>>t; 11 while(t--) 12 { 13 cin >> n; 14 for(int i=1;i<=n;i++)cin>>a[i],f[i]=-(1ll<<60); 15 16 f[n]=a[n]; 17 int s=a[n]; 18 for(int i = n - 1 ; i ;i --)f[i]=max(f[i+1]+a[i],f[i+1]+s+a[i]),s+=a[i]; 19 cout<<f[1]<<'\n'; 20 } 21 return 0; 22 }
4.Problem - 1900A - Codeforces
当连续出现三个"."时,即只需要两个桶,我们可以从前往后遍历整个数组,用f[]表示这一行中连续出现字符.的个数,如果出现#从0开始计数,我们最后在遍历一遍整个字符串数组既可求出最大连续.序列
当最大连续.序列为3及以上时,我们输出2,否则输出.的个数

1 #include<bits/stdc++.h> 2 using namespace std; 3 4 const int N = 2e5 + 10; 5 char a[110]; 6 int f[110]; 7 8 int main() 9 { 10 int t; 11 scanf("%d", &t); 12 while(t -- ) 13 { 14 int n; 15 cin >> n; 16 scanf("%s", a + 1); 17 int ans = 0,cnt = 0; 18 for (int i = 1; i <= n; i ++ ) 19 { 20 f[i] = (a[i] == '.' ? f[i - 1] + 1 : 0); 21 if(a[i] == '.') cnt ++; 22 } 23 24 for (int i = 1; i <= n; i ++ ) 25 { 26 ans = max(ans,f[i]); 27 } 28 29 if(ans >= 3) cout << 2 << endl; 30 else cout << cnt << endl; 31 } 32 }
5.[ABC129D] Lamp - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
这道题与第四题比较类似,我们需要遍历出整个字符串矩阵中最长连续.序列,我们同样可以用dp的思路去做,如果连续出现,次数加+1,否则清零
但是这道题需要我们用四个状态方程去遍历,因为以i,j为起点有四个方向,最后我们的总数减去3即可

1 #include <bits/stdc++.h> 2 using namespace std; 3 #define ll long long 4 5 const int inf = 0x3f3f3f3f; 6 const int N = 2e5 + 5; 7 const int mod = 1e9 + 7; 8 int n,m,ans; 9 char a[2010][2010]; 10 int f1[2010][2010],f2[2010][2010],f3[2010][2010],f4[2010][2010]; 11 12 int main(){ 13 ios::sync_with_stdio(false); 14 cin >> n >> m; 15 for (int i = 1; i <= n; i ++ ) 16 for (int j = 1; j <= m; j ++ ) cin >> a[i][j]; 17 18 for (int i = 1; i <= n; i ++ ) 19 for (int j = 1; j <= m; j ++ ) 20 f1[i][j] = (a[i][j] == '.' ? f1[i][j - 1] + 1 : 0),f3[i][j] = (a[i][j] == '.' ? f3[i - 1][j] + 1 : 0); 21 22 for (int i = n; i; i -- ) 23 for (int j = m; j ; j -- ) 24 f2[i][j] = (a[i][j] == '.' ? f2[i][j + 1] + 1 : 0),f4[i][j] = (a[i][j] == '.' ? f4[i + 1][j] + 1 : 0); 25 26 for (int i = 1; i <= n; i ++ ) 27 for (int j = 1; j <= m; j ++ ) 28 ans = max(ans,f1[i][j] + f2[i][j] + f3[i][j] + f4[i][j] - 3); 29 30 cout << ans << endl; 31 return 0; 32 }
6.数位dp [ABC135D] Digits Parade - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
当不确定位数的时候可以考虑数位dp来计算每一位的不同情况

1 #include <bits/stdc++.h> 2 using namespace std; 3 #define ll long long 4 #define cy cout << "YES" << endl 5 #define cn cout << "NO" << endl 6 7 const int N = 1e5 + 10,inf = 1e9; 8 const int mod = 1e9 + 7; 9 int n,cnt; 10 char s[N]; 11 ll dp[N][13]; //到第i个字符为止,除以13余j(j表示到i-1为止模13余j)有多少种情况 12 13 int main(){ 14 cin >> s + 1; 15 int n = strlen(s + 1); 16 dp[0][0] = 1; //将dp[0][0]初始化 17 for(int i = 1;i <= n;i ++){ 18 if(s[i] != '?'){ 19 s[i] -= '0'; 20 for(int j = 0;j < 13;j ++) 21 dp[i][(j * 10 + s[i]) % 13] = (dp[i][(j * 10 + s[i]) % 13] + dp[i - 1][j]) % mod; 22 } 23 else{ 24 for(int k = 0;k < 10;k ++) 25 for(int j = 0;j < 13;j ++) 26 dp[i][(j * 10 + k) % 13] = (dp[i][(j * 10 + k) % 13] + dp[i - 1][j]) % mod; 27 } 28 } 29 cout << dp[n][5] << endl; 30 }