CF1591F Non-equal Neighbours
Non-Intersecting Subpermutations
Description
给定整数 \(n,m\)。
定义一个长度为 \(n\) 的数列的价值为可以选出满足以下条件的子区间的最多数量总和:
- 子区间的每个元素互不相同;
- 子区间长度为 \(m\);
- 子区间的每个元素取值范围为 \([1,m]\)。
计算数列元素取值范围为 \([1,m]\) 且数列长度为 \(n\) 的所有数列的价值之和,答案对 \(998244353\) 取模。
其中 \(2\le m\le n\le4000\)。
Solution
既然要求每个元素互不相同,就不难想到设计状态为 \(f_{i,j}\) 表示长度为 \(i\) 的数列,末尾连续 \(j\) 位互不相同的价值和。
但是这样设计状态的话,会发现不好转移,因为我们并不知道当前取的区间会不会和之前取的区间冲突。因此我们再设计一个状态为 \(g_{i,j}\) 表示长度为 \(i\) 的数列,末尾连续 \(j\) 位互不相同的方案数。
特殊地,我们令 \(f_{i,0}\) 和 \(g_{i,0}\) 表示末尾连续 \(m\) 位互不相同,即满足条件的方案数。在计算时下标第二维取模即可。
容易推出 \(g\) 的转移:
前半部分表示在原先的部分上再加一个不同的元素。后半部分表示将原先的部分后面接一个已经出现过的元素。比如当前末尾已有 \(3\) 个互不重复的元素为 1,3,4,这时候加入一个 3,将会导致末尾互不重复元素个数变为 \(2\),即 4,3。
接着用 \(g\) 推出 \(f\):
其中 \([p]\) 表示命题 \(p\) 为真是值为 \(1\),否则值为 \(0\)。
前面部分和后面部分与求 \(g\) 同理。中间部分表示当已经凑出符合条件的子区间时的贡献,即方案数。最终答案即为 \(\sum_{i=0}^{m-1} f_{n,i}\)。
这样转移是 \(O(nm^2)\) 的,需要进行优化。
可以发现我们在计算时用到的 \(\sum\) 是 \(f/g\) 的后缀和,因此可以优化为 \(O(nm)\)。
Talk is cheap, show me the code
写的时候为了方便用了刷表法,有些细节自己转换一下。
/*
author:xh_forever
time:2023.9.1
*/
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 4010, mod = 998244353;
int n, m, ans, f[N][N], g[N][N];
signed main(){
scanf("%lld%lld", &n, &m), g[1][1] = m;
for (int i = 1, sum; i <= n; ++i){
sum = 0;
for (int j = m - 1; j; --j) g[i + 1][j] = sum = (sum + g[i][j]) % mod;
for (int j = 0; j < m; ++j) (g[i + 1][(j + 1) % m] += (m - j) * g[i][j] % mod) %= mod;
}
for (int i = 1, sum; i <= n; ++i){
sum = 0;
for (int j = m - 1; j; --j) f[i + 1][j] = sum = (sum + f[i][j]) % mod;
for (int j = 0; j < m; ++j) (f[i + 1][(j + 1) % m] += (m - j) * f[i][j] % mod + (j + 1 == m) * g[i + 1][0]) %= mod;
}
for (int i = 0; i < m; ++i) (ans += f[n][i]) %= mod;
printf("%lld\n", ans);
return 0;
}
浙公网安备 33010602011771号