链接:登录—专业IT笔试面试备考平台_牛客网
来源:牛客网
题目描述
给你一个长度为50的数字串,问你有多少个子序列构成的数字可以被3整除
答案对1e9+7取模
输入描述:
输入一个字符串,由数字构成,长度小于等于50
输出描述:
输出一个整数
示例1
输入
132
132
输出
3
3
示例2
输入
9
9
输出
1
1
示例3
输入
333
333
输出
7
7
示例4
输入
123456
123456
输出
23
23
示例5
输入
00
00
输出
3
3
备注:
n为长度
子任务1: n <= 5
子任务2: n <= 20
子任务3: 无限制
大致思路
对题目进行理解,首先对3取余的结果就三种:0,1,2。判断能否被3整除看的是一个数的每个数字之和,所以可以先用字符数组存数字,转换为int类型数字,拆分为一个一个的数字加入dp数组中。
第二步,dp[][3]数组,3表示这余数的三种情况,预先全部赋值为0,面对一个新数字时,可以选择加入子序列或者不加入子序列。
不加入:不加入的情况好处理,可以直接把上一行的值转移进来。
加入:要重新计算余数情况,状态转移,值转移。加入的步骤要比不加入复杂,我觉得。
方法一:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
const int MOD = 1e9 + 7;
int main() {
int i,j;
string a;
cin>>a;
int n=a.length();
vector<vector<long long>> dp(n + 1, vector<long long>(3, 0));
dp[0][0] = 1; // 空子序列的余数为0,后面的
for(i=1;i<=n;i++){
int p=a[i-1]-'0';
for(j=0;j<3;j++){
//不选
dp[i][j] = dp[i][j]+dp[i - 1][j];//注意这一步,不能只有d[i][j]=d[i-1][j],因为在步骤x中可能会为这个位置提前赋值
int c=(p+j)%3;//新余数
//选
dp[i][c]=dp[i-1][j]+dp[i][c];//步骤x
}
}
cout<< (dp[n][0]-1+MOD)%MOD<<endl;
return 0;
}
方法二:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N = 50+5;
const ll mod = 1000000000+7;
ll a[N],n;
ll f[N][3]={0};
int main()
{
string s;
cin >> s;
n=s.size();
for (ll i=0;i<n;i++)
a[i+1]=s[i]-'0';
for (ll i=1;i<=n;i++)
{
f[i][a[i]%3]=1;
for (ll k=0;k<=2;k++)
f[i][k]=(f[i-1][k] // 不拿当前数字
+f[i-1][(k-a[i]%3+3)%3]//拿**关键理解
+f[i][k])%mod;//累加当前余数的子序列数量
}
cout << f[n][0] << endl;
}
为什么要用 (k - a[i] % 3 + 3) % 3
?
首先,假设当前余数为 k
,我们选择了当前数字 a[i]
后,余数变成了 (k + a[i] % 3) % 3
。那反过来,我们应该知道在选择 a[i]
之前的余数是多少,这样才能更新状态。
具体来说:
当前余数 k
是通过 k'
(前一个余数)加上 a[i] % 3
后得到的,即:
k=(k′+a[i]%3)%3
由此可得:
k′=(k−a[i]%3)%3k' = (k - a[i] \% 3) \% 3k′=(k−a[i]%3)%3
但是,问题在于,减法可能会导致负数,特别是如果 k
小于 a[i] % 3
的话。比如,k = 0
和 a[i] % 3 = 1
时,k - a[i] % 3 = 0 - 1 = -1
,这就不是我们想要的余数了。
为了避免负数余数的情况,我们在计算过程中加上 +3
,然后再取模 3。这保证了结果始终在 [0, 2]
的合法范围内。也就是说,(k - a[i] % 3 + 3) % 3
的作用是把负余数“修正”到合法的范围 [0, 2]
。
举例:
假设:
- 当前余数
k = 0
- 当前数字
a[i] = 4
,所以a[i] % 3 = 1
我们知道,选择当前数字后,余数会变成 (k + a[i] % 3) % 3 = (0 + 1) % 3 = 1
。
但是我们希望倒推,找到前一个余数 k'
,使得:
即:
k′=(0−1)%3=−1%3=2k' = (0 - 1) \% 3 = -1 \% 3 = 2k′=(0−1)%3=−1%3=2
为了得到正确的 k'
,我们加上 3
,并取模 3
,即:
(k−a[i]%3+3)%3=(0−1+3)%3=2(k - a[i] \% 3 + 3) \% 3 = (0 - 1 + 3) \% 3 = 2(k−a[i]%3+3)%3=(0−1+3)%3=2
这就是我们倒推得到的正确前一个余数 k' = 2
。
方法三:
#include<iostream>
#include<string>
#include<vector>
const long long N =1e9+7;
using namespace std;
int main(){
string s;
cin>>s;
vector<vector<int>>dp(3,vector<int>(50,0));
int m=(s[0]-'0')%3,len=s.size();
dp[m][0]=1;
for(int i=1;i<len;i++){
m=(s[i]-'0')%3;
if(m==0){
dp[0][i]=(dp[0][i-1]*2+1)%N;
dp[1][i]=(dp[1][i-1]*2)%N;
dp[2][i]=(dp[2][i-1]*2)%N;
}
if(m==1){
dp[0][i]=(dp[0][i-1]+dp[2][i-1])%N;
dp[1][i]=(dp[1][i-1]+dp[0][i-1]+1)%N;
dp[2][i]=(dp[2][i-1]+dp[1][i-1])%N;
}
if(m==2){
dp[0][i]=(dp[0][i-1]+dp[1][i-1])%N;
dp[1][i]=(dp[1][i-1]+dp[2][i-1])%N;
dp[2][i]=(dp[2][i-1]+dp[0][i-1]+1)%N;
}
}
cout<<dp[0][len-1]<<endl;
return 0;
}
方法二和三都是参考了大牛的,有错误请求指正