0
点赞
收藏
分享

微信扫一扫

【牛客 - 21302】被3整除的子序列(线性dp)

题干:

给你一个长度为50的数字串,问你有多少个子序列构成的数字可以被3整除
答案对1e9+7取模

输入描述:

输入一个字符串,由数字构成,长度小于等于50

输出描述:

输出一个整数

示例1

输入

复制

132

输出

复制

3

示例2

输入

复制

9

输出

复制

1

示例3

输入

复制

333

输出

复制

7

示例4

输入

复制

123456

输出

复制

23

示例5

输入

复制

00

输出

复制

3

备注:

n为长度
子任务1: n <= 5
子任务2: n <= 20
子任务3: 无限制

解题报告:

  不难发现长度为50的串最大可以到达的数字就是50*9 = 450 ,所以dp[i][j]代表前i个数 可以组成的和为j的方法数。然后分选和不选两种决策转移就行了。

AC代码:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<queue>
#include<map>
#include<vector>
#include<set>
#include<string>
#include<cmath>
#include<cstring>
#include<cctype>
#define ll long long
#define pb push_back
#define pm make_pair
using namespace std;
const int MAX = 2e5 + 5;
char s[MAX];
ll dp[55][555];//dp[i][j]代表前i个数 可以组成的和为j的方法数。
const ll mod = 1e9 + 7;
int f(char c) {
return c - '0';
}
int main()
{
//dp[0][0]=1;
cin>>(s+1);
int len = strlen(s+1);
for(int i = 1; i<=len; i++) {
// dp[i][f(s[i])] = 1;
for(int j = 0; j<555; j++) {
dp[i][j] = dp[i-1][j];
if(j == f(s[i])) dp[i][j]++;
if(j >= f(s[i])) dp[i][j] += dp[i-1][j-f(s[i])];
dp[i][j] %= mod;
}
}
//printf("**%d\n",dp[len][3]);
ll ans = 0;
for(int j = 0; j<555; j++) if(j % 3 == 0) ans = (ans + dp[len][j]) % mod;
printf("%lld\n",ans);

return 0 ;
}

AC代码2:(这样写更简短一点)

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<queue>
#include<map>
#include<vector>
#include<set>
#include<string>
#include<cmath>
#include<cstring>
#include<cctype>
#define ll long long
#define pb push_back
#define pm make_pair
using namespace std;
const int MAX = 2e5 + 5;
char s[MAX];
ll dp[55][555];//dp[i][j]代表前i个数 可以组成的和为j的方法数。
const ll mod = 1e9 + 7;
int f(char c) {
return c - '0';
}
int main()
{
//dp[0][0]=1;
cin>>(s+1);
int len = strlen(s+1);
for(int i = 1; i<=len; i++) {
// dp[i][f(s[i])] = 1;
for(int j = 0; j<555; j++) {
dp[i][j] = dp[i-1][j];
if(j >= f(s[i])) dp[i][j] += dp[i-1][j-f(s[i])];
dp[i][j] %= mod;
}
dp[i][f(s[i])]++;
}
ll ans = 0;
for(int j = 0; j<555; j++) if(j % 3 == 0) ans = (ans + dp[len][j]) % mod;

printf("%lld\n",ans);

return 0 ;
}

 注意到这题不能直接想当然的dp[0][0]=0,,如果是这样的话那代码就更简短了,,为什么不能这样呢?因为这题中 0 也算状态之一,所以不能这样来初始化,考虑一般写dp[0][0]的影响,无非就是加上自己的一次。所以我们虽然不写dp[0][0]=0了,但是相应的把这个状态自己加上就行了。注意AC代码2,这一句++必须放在for之后,不能放在for之前,因为不然后面for中那个等号就把这个++ 操作给覆盖了。(其实放在for之前也行,但是就得变成dp[i][j] += dp[i-1][j];了?)

同时通过观察我们发现,最后答案只跟模数有关,所以我们直接记录模数也可以。

AC代码3:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<queue>
#include<map>
#include<vector>
#include<set>
#include<string>
#include<cmath>
#include<cstring>
#include<cctype>
#define ll long long
#define pb push_back
#define pm make_pair
using namespace std;
const int MAX = 2e5 + 5;
char s[MAX];
ll dp[55][3];//dp[i][j]代表前i个数 可以组成的和为j的方法数。
const ll mod = 1e9 + 7;
int f(char c) {
return c - '0';
}
int main()
{
//dp[0][0]=1;
cin>>(s+1);
int len = strlen(s+1);
for(int i = 1; i<=len; i++) {
// dp[i][f(s[i])] = 1;
for(int j = 0; j<3; j++) {
dp[i][j] = dp[i-1][j];
dp[i][j] += dp[i-1][(j+15-f(s[i]))%3];
dp[i][j] %= mod;
}
dp[i][f(s[i])%3]++;
}
ll ans = dp[len][0];
//for(int j = 0; j<555; j++) if(j % 3 == 0) ans = (ans + dp[len][j]) % mod;

printf("%lld\n",ans%mod);

return 0 ;
}

 


举报

相关推荐

0 条评论