被3整除的子序列
[小菜鸡第一次写题解,请多多指教]
3的倍数特点:所有数位数字之和是3的倍数。
算法思路:二维dp
- 集合表示:f[i][j]表示长度为i的字符串中除以3后余数为j
- 集合含义:f[i][j]表示子串的个数
- 状态转换:将集合划分为两部分,第一部分为a[i](第i个元素)除以3余数等于j,第二部分为a[i]除以3余数不等j。
现在我们来考虑状态转移方程:f[i][j]可以先考虑为两部分转移过来的:
- 第一部分)为长度是i-1的字串除以3余数是j的个数;
- 第二部分)为长度是i-1的字串除以3余数是k的个数,这一部分的子串和新加入的a[i]可以组合成新的除以3后余数为j,所以k满足(a[i] % 3 + k) % 3 == j
用代码表示为:
//对于每一个f[i][j]
//先求一下k的值
int k;
for(k = 0; k < 3; k++)
{
if((k + a[i] % 3) % 3 == j)
break;
}
//状态转移方程为:f[i][j] = f[i - 1][j] + f[i - 1][k]
//需要注意的是:a[i]模3的余数可能是j,若为j的话,f[i][j]相对于(f[i - 1][j] + f[i - 1][k])要新加如一个a[i],所以要加1
if(a[i] % 3 == j) f[i][j] = (f[i - 1][j] + f[i - 1][k] + 1) % mod;
else f[i][j] = (f[i - 1][j] + f[i - 1][k]) % mod;
所以将以上整合,代码为:
#include<iostream>
#include<cstring>
using namespace std;
const int N = 55, mod = 1e9 + 7;
char s[N];
int a[N];
int f[N][N];//长度为i的模3余数是j的字串个数
int main()
{
scanf("%s", s);
int n = strlen(s);
for(int i = 0; i < n; i++) a[i + 1] = s[i] - '0';
for(int i = 1; i <= n; i++)
for(int j = 0; j < 3; j++)
{
//对于每一个f[i][j]
//先求一下k的值
int k;
for(k = 0; k < 3; k++)
{
if((k + a[i] % 3) % 3 == j)
break;
}
//状态转移方程为:f[i][j] = f[i - 1][j] + f[i - 1][k]
//需要注意的是:a[i]模3的余数可能是j,若为j的话,f[i][j]相对于(f[i - 1][j] + f[i - 1][k])要新加如一个a[i],所以要加1
if(a[i] % 3 == j) f[i][j] = (f[i - 1][j] + f[i - 1][k] + 1) % mod;
else f[i][j] = (f[i - 1][j] + f[i - 1][k]) % mod;
}
printf("%d", f[n][0]);
return 0;
}
补充知识点:1. C++中字符数组的输入输出 2. C++中如何表示字符数组的字符长度(strlen())