题目链接
题目描述
思路
子序列定义:某个序列的子序列是在原序列的基础上去掉一些元素(或不去)但不破坏剩下元素的相对位置的序列(以原序列abcde为例,去掉c元素,原序列的子序列就是abde,而abed就不是,因为它破坏了原序列的相对位置)。
那么此题的每位上的元素就可以看成取和不取两种状态,题目问的是被3整除的子序列数,且能被3整除的数各个位数字之和对3取余为0,所以每次判断完一位数之后只需记录余数为0,1,2这3种状态即可。这一想直接拿DP做:创建dp[51][3],dp[ i ][ j ]的值的含义是处理前i个元素之后,各位数之和对3取余以后子序列的个数,那dp[ i ][0]就是前 i 位数的子序列可以被3整除的个数。长度为n的序列的可被3整除的子序列个数就是dp[ n ][0].
DP的核心无非3要素:1.DP的初始化;2.状态转移方程;3.结果的表示。
A.DP的初始化:处理到第 i 位时,本位元素对3取余,记余数为m(方便下文解释和理解),然后dp[ i ][m]赋值为1,此步的意思是第 i 个元素做子序列的头。
B.DP的状态转移:处理到第 i 位时,做完A步(其实这题A步骤也可以看成状态转移的一部分)后,遍历dp[i][0~2],不同于A的是,此次将第 i 个元素看作是子序列的尾端。根据上述提到的,第 i 个数分为两种情况:(1)第 i 个数不加入子序列:那么本位就继承上一位的数据,即dp[ i ][?]+=dp[ i-1 ][?];(2)第 i 个数加入子序列:处理到dp[ i ][ j ]时,因为本位对3取余已有m,所以对于余数为 j 时,只需继承前数余数的j-m就行,但是考虑到j-m可能小于0,所以j-m替换为(j-m+3)%3,这样同时也保证了j-m+3不会超过2。整合起来状态转移方程就是dp[ i ][ j ]+=(dp[ i-1 ][ j ]+dp[i-1][(j+3-m)%3])%inf;(inf为题目的结果取余量,即1e9+7)。
C.结果表示为dp[ n ][0](上已述,此不赘述)。
AC代码
#include<bits/stdc++.h>
using namespace std;
#define maxn 51//数字串最大的长度
#define inf 1000000007//取模数
int n,m,dp[maxn][3];//n表示数字串长度,m用来存储余数,dp[i][j]的值的含义是处理前i个元素之后,各位数之和对3取余以后子序列的个数
string s;//接收数字串
int main(){
cin>>s;//输入数字串
n=s.size();//求出数字串的长度
for(int i=1;i<=n;i++){//i表示处理到第几个元素
m=(s[i-1]-'0')%3;//m记录本位对3取余
dp[i][m]+=1;//将本位做子序列的开头
for(int j=0;j<=2;j++){//本位各种情况记录
dp[i][j]+=(dp[i-1][j]+dp[i-1][(j+3-m)%3])%inf;//dp[i-1][j]:本位不算入前面子序列尾端;dp[i-1][(j+3-m)%3]表示本位算入前面子序列尾端
}
}
cout<<dp[n][0];//输出结果
}