被3整除的子序列
线性 dp
如果知道:一个数如果可以被3整除,那么这个数各位和一定可以被3整除,这个题就比较好做了
(验证了一下1-9只有3满足这个性质)
闫氏dp分析法
首先我们要明确动态变量都有什么:以
i
i
i 结尾,以
i
i
i 结尾的子序列各位和
%
3
\%3
%3 取模为
j
∈
(
0
,
1
,
2
)
j \in (0,1,2)
j∈(0,1,2)
1.状态表示:
f
[
i
]
[
j
]
f[i][j]
f[i][j] 以第
i
i
i 位结尾,各位和
%
3
\%3
%3 为
j
j
j 的集合数
2.
f
[
i
]
[
j
]
f[i][j]
f[i][j] 方案数
3.集合划分:每次转移都是从
f
[
1
,
2
,
3...
i
−
1
]
[
0
,
1
,
2
]
f[1,2,3...i-1][0,1,2]
f[1,2,3...i−1][0,1,2]
第三层for循环其实就是以
i
i
i 结尾的已经确定了,在后边加上一个字符
s
[
k
]
s[k]
s[k] ,更新以
k
k
k 结尾的子序列
复杂度:
O
(
n
2
)
O(n^2)
O(n2)
(感觉这个思路比较容易理解
AC代码:
#include<bits/stdc++.h>
#define ll long long
#define _ 0
using namespace std;
const int maxn = 5e3 + 9;
const int mod = 1e9 + 7;
ll n, m, ans;
ll f[59][3];
char s[59];
void work()
{
scanf("%s",s + 1);
n = strlen(s + 1);
for(int i = 1; i <= n; ++i)// 初始化边界
f[i][(s[i] - '0') % 3] = 1;
for(int i = 1; i <= n; ++i)
for(int j = 0; j < 3; ++j)
for(int k = i + 1; k <= n; ++k)
f[k][(j + s[k] - '0') % 3] = (f[k][(j + s[k] - '0') % 3] + f[i][j]) % mod;
ll ans = 0;
for(int i = 1; i <= n; ++i) ans = (ans + f[i][0]) % mod;
cout << ans << endl;
}
int main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
//int TT;cin>>TT;while(TT--)
work();
return ~~(0^_^0);
}
其他dp方法
也是要首先明确动态变量,也就是状态:前
i
i
i 位字符组成的子序列,各位和
%
3
\%3
%3 为
j
∈
(
0
,
1
,
2
)
j\in (0,1,2)
j∈(0,1,2)
设
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j] 为前
i
i
i 位字符组成的子序列中,各位和
%
3
\%3
%3 为
j
j
j 的集合数
那么包含前面的选择,对于第
i
i
i 位有选与不选两种决策:
设第
i
i
i 位为数字
%
3
=
m
\%3=m
%3=m
如果不选择第
i
i
i 位,那么对答案没有贡献,直接加上
d
p
[
i
−
1
]
[
j
]
dp[i-1][j]
dp[i−1][j] 的答案,这时候是前
i
−
1
i-1
i−1 个字符组成的子序列最后没有加上第
i
i
i 位。
如果选择第
i
i
i 位,首先它自己单独成一个序列(即初始化),会对
d
p
[
i
]
[
m
]
dp[i][m]
dp[i][m] 贡献
1
1
1 ,然后考虑给前
i
−
1
i-1
i−1 个字符组成的所有子序列末尾加上第
i
i
i 位,当前各位和为
j
j
j,那么它应该由各位和为
p
=
(
j
−
m
+
3
)
%
3
p=(j-m+3)\%3
p=(j−m+3)%3 的状态转移过来,使得
(
p
+
m
)
%
3
=
j
(p+m)\%3=j
(p+m)%3=j
这篇博客有助于理解
那么状态转移方程
选:
d
p
[
i
]
[
j
]
+
=
d
p
[
i
−
1
]
[
(
j
−
m
+
3
)
%
m
o
d
]
dp[i][j] += dp[i-1][(j-m+3)\%mod]
dp[i][j]+=dp[i−1][(j−m+3)%mod]
不选:
d
p
[
i
]
[
j
]
+
=
d
p
[
i
−
1
]
[
j
]
dp[i][j] + = dp[i-1][j]
dp[i][j]+=dp[i−1][j]
最终答案:
d
p
[
n
]
[
0
]
dp[n][0]
dp[n][0]
复杂度:
O
(
n
)
O(n)
O(n)
AC代码:
#include<bits/stdc++.h>
#define endl '\n'
#define ll long long
#define ull unsigned long long
#define ld long double
#define all(x) x.begin(), x.end()
#define eps 1e-6
using namespace std;
const int maxn = 2e5 + 9;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
ll n, m;
string s;
int f[100][3];
void work()
{
cin >> s;
n = s.size(); s = "@" + s;
for(int i = 1; i <= n; ++i)
{
for(int j = 0; j < 3; ++j) f[i][j] = f[i-1][j];// 计数先承接上一层的
f[i][(s[i] - '0') % 3] += 1; // 更新这一层的
for(int j = 0; j < 3; ++j)// 转移
{
int k = (s[i] - '0' + j) % 3;
f[i][k] = (f[i][k] + f[i-1][j]) % mod;
}
}
cout << f[n][0];
}
int main()
{
ios::sync_with_stdio(0);
// int TT;cin>>TT;while(TT--)
work();
return 0;
}