题目描述
要求n位数的数字A中任意5位的和不能超过m,而且奇数位必须时奇数,偶数位必须是偶数,问这样的数字A有多少种,要求输出结果与998244353求模后的数字。输入条件: 5 ≤ n ≤ 2 × 1 0 5 5 \le n \le2\times10^5 5≤n≤2×105, 0 ≤ m ≤ 50 0 \le m \le 50 0≤m≤50。
解析
此题应该使用动态规划。设
p
i
p_i
pi表示数
i
i
i在A中的位次,
f
(
p
i
)
f(p_i)
f(pi)表示A中前
p
i
p_i
pi位满足题设条件的分布种数。设dp[a][b][c][d]表示后4位数分别选择a、b、c、d时前
p
a
p_a
pa位数字的可能分布情况。则有
d
p
′
[
a
]
[
b
]
[
c
]
[
d
]
dp'[a][b][c][d]
dp′[a][b][c][d] =
∑
e
:
奇
/
偶
,
a
+
b
+
c
+
d
+
e
<
=
m
d
p
[
b
]
[
c
]
[
d
]
[
e
]
。
\sum_{e : 奇/偶, a + b + c + d + e <= m}dp[b][c][d][e]。
∑e:奇/偶,a+b+c+d+e<=mdp[b][c][d][e]。如下图所示,
p
a
=
6
p_a=6
pa=6,
f
(
p
a
)
=
∑
a
,
b
,
c
,
d
d
p
[
a
]
[
b
]
[
c
]
[
d
]
f(p_a) = \sum_{a, b, c, d}dp[a][b][c][d]
f(pa)=∑a,b,c,ddp[a][b][c][d]。需要注意,在遍历a,b,c,d,e时都要考虑奇偶。
这个状态转移公式怎么来的呢,注意看上图中1-5这五位与2-6这五位有4位是共用的,当2-6位中每有一个符合“和不超过m”的分布时,需要加上该分布下第2位之前的数的可能分布数量,才能得到该分布下前6位数字可能的分布数量(即dp[a][b][c][d])。那么该分布下第2位之前的符合条件的分布数量就是dp[b][c][d][e],2-6位中只有这前4位e、d、c、b与前面有关。
代码
#include <vector>
#include <iostream>
using namespace std;
#define MOD 998244353 // 把MOD声明为预编译符号或者静态常量可以很大提高性能
int dp[2][10][10][10][10];
int m, n;
int main()
{
// 请在此输入您的代码
cin >> n >> m;
// 初始化
for (int a = 0; a < 10; a += 2)
for (int b = 1; b < 10; b += 2)
for (int c = 0; c < 10; c += 2)
for (int d = 1; d < 10; d += 2)
dp[1][a][b][c][d] = 1;
// 迭代
for (int i = 5; i <= n; ++i)
{
int p = i % 2;
for (int a = p; a < 10; a += 2)
for(int b = 1 - p; b < 10; b += 2)
for (int c = p; c < 10; c += 2)
for (int d = 1 - p; d < 10; d += 2)
{
int res = 0;
auto &temp = dp[i % 2][b][c][d];
for (int k = p; k < 10; k += 2)
if (a + b + c + d + k <= m)
res = (res + temp[k]) % MOD;
dp[1 - p][a][b][c][d] = res;
}
}
// 得出结果
int ans = 0;
int p = n % 2;
for (int a = p; a < 10; a += 2)
for(int b = 1 - p; b < 10; b += 2)
for (int c = p; c < 10; c += 2)
for (int d = 1 - p; d < 10; d += 2)
{
ans = (ans + dp[1 - p][a][b][c][d]) % MOD;
}
cout << ans << endl;
return 0;
}