dp训练1.被3整除的子序列

链接:登录—专业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 = 0a[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;
}

方法二和三都是参考了大牛的,有错误请求指正

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值