游游的9的倍数

链接:https://ac.nowcoder.com/acm/contest/60245/D
来源:牛客网

题目描述
游游拿到了一个数字串,她想取一个该数字串的子序列(子序列在原串中可以不连续),使得该子序列是9的倍数。子序列可以包含前导零。
游游想知道,一共能取多少个合法的子序列?答案请对
109+7取模。
我们定义,若两个子序列在原串中的位置不同,则认为它们不同。

输入描述:
一个长度不超过200000的,仅由’0’~‘9’ 十种字符组成的字符串。
输出描述:
子序列是9的倍数的数量。答案请对 10 9+7 取模。

示例1
输入
1188

输出
5

说明
共可以取4个不同的"18"子序列,和一个"1188"子序列,都是9的倍数。

示例2
输入
0123

输出
1

说明
只有子序列"0"是9的倍数。

题意:

给出一个字符串,你从中选取子序列,可以不连续的选择,使得选出来的序列表示的数字是 9 的倍数。问一共可以选出多少种子序列?(若两个子序列在原串中的位置不同,则认为它们不同。)

思路:
首先明确一点,一个数字如果是 9 的倍数的话,那么这个数字的所有数位之和是9的倍数。

ok,明确了这一点后,除法问题就变成了加法问题,也就是说从头开始走,当某段数字和是 9 的倍数的时候,这个数字就能是9的倍数,那么答案也就++。

那这样的话,就会发现,到某个位置之后,到该位置结尾的序列一共有9总可能,即除以9之后余数是1 ~ 9,到下一个位置的时候就可以通过前一个位置9种余数的情况转移到当前状态上来。

具体来讲,我们先从头遍历一遍字符串,记录到每一个位置的时候以当前位置为结尾余数为 j 的数量,j是1~9,0和9是一样的,所以算在一起。

然后从头开始遍历,i = 1 到 i = n ,a[i] 就是第 i 个位置的数字。 这时候我们定义一个 j 的循环,j 从 0 到 8 , 因为余数有9种可能。 dp[i][j]表示的意义为 i 位置及之前余数是 j 的数的数量,那状态转移方程就可以写为:

dp[i][(j+a[i])%9] = (dp[i][(j+a[i])%9] + dp[i-1][j] + dp[i-1][(j+a[i])%9])%M;

dp[i][(j+a[i])%9]:i 位置及之前余数是 (j+a[i])%9 的数量。
它可以由什么得来呢?
首先是前一个位置余数是 (j+a[i])%9 的数量,也就是dp[i-1][(j+a[i])%9],也就是说不算当前 i 位置的数,就已经有这些数量了。

其次就是由当前的这个数转移而来,那当前这个数和 j 相加之后,余数就是 (j+a[i])%9 , 那数量是多少呢?是当前 i 位置余数是 (j+a[i])%9 的原本的值 加上 前一个位置余数是 j 的值。

可能不好理解?我给你举个例子,就第一个1188的例子

dp[4][1] = dp[3][1] + dp[4][1] + dp[3][2]

这个状态转移方程看看各位萌新大佬能理解不?

dp[4][1]表示的是i=4的时候,余数是1的结果的数量。
那她可以由什么转移而来呢!?

可以是dp[3][1],也就是前一个位置余数是 1 的值.

再加上当前位置余数是1的原本的值dp[4][1]加上前一个位置余数是2的值。为什么这里余数是2呢?

你这么看肯定是看不懂的啊,你得看dp[4][1]里面的 1 是怎么来的,是a[i]+j 得到的,也就是(8+2)%9=1。看到这有没有一种恍然大悟的感觉,因为dp[4][1]里面的1是两部分组成,一个是a[i],一个是 j ,所以要想得到
dp[i][(j+a[i])%9],就应该是当前位置余数是(j+a[i])%9的数量加上当前位置之前的余数是 j 的数量,这样才是总数量。

不理解的话再多读几遍,然后试着写一下 dp[4][2],你就会真正理解其中的原委,答案我偷偷放在下面了哦

dp[4][2] = dp[3][2] + dp[4][2] + dp[3][3]

代码:

#include<bits/stdc++.h>
using namespace std;

const int M = 1000000007;
const int N = 200005;

string s;
long long int a[N] , dp[N][10];

int main(){
    cin >> s;
    int len = s.length();
    for(int i = 0 ; i < len ; i++){
        int k = s[i] - '0';
        a[i+1] = k;
        dp[i+1][k%9]++;
    }
    int n = len;
    for(int i = 1 ; i <= n ; i++){
        for(int j = 0 ; j <= 8 ; j++){
            dp[i][(j+a[i])%9] = (dp[i][(j+a[i])%9] + dp[i-1][j] + dp[i-1][(j+a[i])%9])%M;
        }
    }
    cout << dp[n][0]%M << endl;
    return 0;
}




  • 5
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值