对于牛客的竞赛培训 动态规划的第一题
就给我来了个区间dp 的题 这个也太猛了吧
废话不多说 直接上 题
链接:https://ac.nowcoder.com/acm/problem/21302
来源:牛客网
题目描述
给你一个长度为50的数字串,问你有多少个子序列构成的数字可以被3整除
答案对1e9+7取模
输入描述:
输入一个字符串,由数字构成,长度小于等于50
输出描述:
输出一个整数
在这之前我没接触过区间dp的题目 这个题算是自己的区间dp 的一个开端吧
但是我也是看大佬的博客所理解的 所以 如果说的不好 或者不清楚 希望大佬们在评论区 给我反馈
好了 说这个题吧
因为是子序列 所以可以不连续(所以用区间dp的方法做这个题)
因此需要保存个子序列对3取余 余数为0 1 2 的时候
这里我们用dp[i][k]表示 从0到i的区间上 余数为k 的数目
然后我们再说状态转移方程
如果扩充一个数 该数为s[i]%3==1 那么下一个
dp[i][0]=dp[i-1][2]+dp[i-1][0]
dp[i][1]=dp[i-1][1]+dp[i-1][0]+1
dp[i][2]=dp[i-1] [1]+dp[i-1][2];
如果扩充一个数 该数为s[i]%3==2 那么下一个
dp[i][0]=dp[i-1][0]+dp[i-1][1]
dp[i][1]=dp[i-1][1]+dp[i-1][2]
dp[i][2]=dp[i-1][0]+dp[i-1][2]+1
如果扩充一个数 该数为s[i]%3==0 那么下一个
dp[i][0]=2*dp[i-1][0]+1
dp[i][1]=2*dp[i-1][1]
dp[i][2]=2*dp[i-1][2]
并且每次循环后需要对dp[i][j]取余 mod 防止越界
最后答案为dp[n-1][0] 表示0到n-1的区间上能被3整除的数的个数
代码如下:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define db double
#define MAX 1000000
#define rep(i,j,k) for(int i=(int)(j);i<=(int)(k);i++)
#define per(i,j,k) for(int i=(int)(j);i>=(int)(k);i--)
long long mod = 1e9 + 7;
ll dp[55][10];
int main()
{
string s;
cin >> s;
//区间dp
//状态转移方程:
/*
dp[i][0]=dp[i-1][0]+dp[i-1][2];
dp[i][1]=dp[i-1][0]+dp[i-1][1]+1;
dp[i][2]=dp[i-1][1]+dp[i-1][2];
*/
//初始dp全部为1
for (int i = 0; i < s.length(); i++) {
dp[i][(s[i] - '0') % 3] = 1;
}
for (int i = 1; i < s.length(); i++) {
if ((s[i] - '0') % 3 == 0) {
dp[i][0] = 2 * dp[i - 1][0] + 1; //原本余数为0 加上一位后 除了是之间的两倍还需要加上之间本身的1
dp[i][1] = 2 * dp[i - 1][1];
dp[i][2] = 2 * dp[i - 1][2];
}
else if ((s[i] - '0') % 3 == 1)
{
dp[i][0] = dp[i - 1][0] + dp[i - 1][2];
dp[i][1] = dp[i - 1][0] + dp[i - 1][1] + 1;
dp[i][2] = dp[i - 1][1] + dp[i - 1][2];
}
else
{
dp[i][0] = dp[i - 1][1] + dp[i - 1][0];
dp[i][1] = dp[i - 1][1] + dp[i - 1][2] ;
dp[i][2] = dp[i - 1][2] + dp[i - 1][0] + 1;
}
for (int j = 0; j < 3; j++) {
dp[i][j] %= mod;
}
}
cout << dp[s.length()-1][0] << endl;
//system("pause");
return 0;
}