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

版权声明:欢迎学习我的博客,希望ACM的发展越来越好~ https://blog.csdn.net/qq_41289920/article/details/88049573

题干:

给你一个长度为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 ;
 }

猜你喜欢

转载自blog.csdn.net/qq_41289920/article/details/88049573