牛客-被3整除的子序列

题目描述

你喜欢3070显卡吗?不,这不重要,看下面。

给你一个长度为50的数字串,问你有多少个子序列构成的数字可以被3整除

答案对1e9+7取模,子序列不一定连续

输入描述

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

输出描述

输出一个整数

输入样例 1 

132

输出样例 1

3

输入样例 2 

9

输出样例 2

1

输入样例 3 

333

输出样例 3

7

输入样例 4 

123456

输出样例 4

23

输入样例 5 

00

输出样例 5

3

提示

n为长度
子任务1: n <= 5
子任务2: n <= 20
子任务3: 无限制
用动态规划写,可以找规律试一试

 解题思路

看完题目,想法就是暴力搜索,把它的每一个子序列都判断一下,但是数字串长度最长为50,选取它的每一个子序列就是C(n,1)+...+C(n,n)=2^n-1(n为50)种可能;超时不行。

既然不行我们可以观察一下数字串之间是不是有什么特殊的连接(关系);

举一个例子,数字串123的所有子序列为1,2,3,12,13,23,123;我们对此进行判断发现

所有位数相加%3为0的是3,12,123;3个

为1的是1,13;2个

为2的是2,23;2个

那么数字串123有3个满足条件的子序列

现在我们在后面加4变成数字串1234,它的所有子序列为1,2,3,12,13,23,123,14,24,34,,124,134,234,1234,我们将其和数字串123的子序列对比可以发现,1234的子序列在包含123的子序列的基础上,增加了在原来子序列后面加4和4的子序列,这意味着我们判断1234有多少个满足条件的子序列可以根据123的情况推出,如下

原来的子序列判断过了,不需要重新判断,

4%3为1,则增加的新子序列满足条件的是由原来%3为2得到的

1234的子序列中满足条件的个数为   原来满足条件的子序列+原来%3为2的子序列

以此类推,我们可以对数字串从头到尾遍历,由前面的情况和新加数来推出现在的情况,则就需要一个数组dp[i][j]来存储不同长度的情况(dp[i][j]表示数字串前i位的所有子序列中%3为j的个数,j为0,1,2)

状态转移方程为

dp[i][j]=dp[i-1][j]+dp[i-1][x]((x+(新加数%3))%3=j,特殊情况:当j=新加数%3时,需要加1,因为新加数可以单独为子序列)

题解代码

简写形式

#include <bits/stdc++.h>

using namespace std;

int main()
{
    int N = 1e9 + 7;
    string str;
    int len;
    cin>>str;
    len=str.length();
    int arr[len+5][3];///存储前j位字符串子串除3余数为i的个数
    memset(arr,0,sizeof(arr));
    int x=(str[0]-'0')%3;
    arr[0][x]++;
    for(int i=1;i<len;i++){
        x=(str[i]-'0')%3;
        arr[i][(0+x)%3]=arr[i-1][0]+arr[i-1][(0+x)%3];
        arr[i][(1+x)%3]=arr[i-1][1]+arr[i-1][(1+x)%3];
        arr[i][(2+x)%3]=arr[i-1][2]+arr[i-1][(2+x)%3];
        arr[i][x]++;
        arr[i][0]=arr[i][0]%N;
        arr[i][1]=arr[i][1]%N;
        arr[i][2]=arr[i][2]%N;
    }
    //cout<<arr[0][0]<<" "<<arr[0][1]<<" "<<arr[0][2]<<endl;
    cout<<arr[len-1][0]<<endl;
}

分情况讨论 

#include<bits/stdc++.h>
#define N 1000000007
using namespace std;
int dp[50][3];
int main()
{
    string s;
    cin>>s;
    int m=(s[0]-'0')%3;
    dp[0][m]=1;
    for(int i=1;i<s.size();i++)
        {
            m=(s[i]-'0')%3;
            switch(m)
            {
                case 0:
                {
                    dp[i][0]=(dp[i-1][0]*2+1)%N;
                    dp[i][1]=(dp[i-1][1]*2)%N;
                    dp[i][2]=(dp[i-1][2]*2)%N;
                    break;
                }
                case 1:
                {
                    dp[i][0]=(dp[i-1][0]+dp[i-1][2])%N;
                    dp[i][1]=(dp[i-1][1]+dp[i-1][0]+1)%N;
                    dp[i][2]=(dp[i-1][2]+dp[i-1][1])%N;
                    break;
                }
                case 2:
                {
                    dp[i][0]=(dp[i-1][0]+dp[i-1][1])%N;
                    dp[i][1]=(dp[i-1][1]+dp[i-1][2])%N;
                    dp[i][2]=(dp[i-1][2]+dp[i-1][0]+1)%N;
                    break;
                }
            }
        }
    cout<<dp[s.size()-1][0];
}

差不多就这样吧,我也暂时想不到什么了,如果看完之后有问题可以在下面评论或者直接私信我,我看到了就会回复的。

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值