[数学] [dp优化] CF1740F Conditional Mix
posted on 2024-07-25 00:13:10 | under | source
首先,每次选择两个交集为空的集合合并,等价于最终将原集合恰好拆分为若干个集合,每个集合内部无重复元素。
不妨记这些集合为 \(A=\{M_1\dots M_n\}\),假如不够就补 \(0\) 即可,令 \(|M_1|\ge |M_2|\dots \ge |M_n|\)。然后记 \(cnt_i\) 为原序列中元素 \(i\) 的出现次数。
考虑判定 \(A\) 是否合法,注意到非常重要的一点:假如存在 \(M_i>M_j\),那么 \(M_i\) 中必然存在 \(M_j\) 中没有的元素,可以调整一元素,令 \(M_i-1\) 而 \(M_j+1\)。
于是不难想到,若有 \(S=\{N_1\dots N_n\}\),且满足“字典序”(即从 \(i=1\) 开始比较集合大小)比 \(A\) 大,那么就能将 \(S\) 调整为 \(A\),即 \(S\) 合法,\(A\) 一定合法。
于是考虑找到一个合法且“字典序”最大的集合 \(P\),容易想到,贪心地从 \(i=1\) 开始尽量填满即可。
不过比较“字典序”还是太麻烦了,其实可以这样比较,假如 \(\forall i,\sum_{j=1}^i |N_j|\ge \sum_{j=1}^i |M_j|\),那么就有 \(S>A\)。
对于刚刚构造的 \(P\) 集合,显然有 \(\sum_{j=1}^i |P_j|=\sum_{j=1}^n \min(i, cnt_j)\)。
我们记 \(P,A\) 前缀和分别为 \(Lim_i,pre_i\),那么只要满足 \(\forall i,pre_i\le Lim_i\) 就有 \(A\) 合法。
于是就容易了,不妨设 \(f_{i,s,j}\) 为填了 \(A_1\dots A_i\),且 \(pre_i=s,|A_i|=j\) 时,合法方案总数,转移考虑枚举 \(|A_{i-1}|=k\ge j\) 即可。
于是得到了 \(O(n^4)\) 暴力,可以前缀和优化至 \(O(n^3)\),然后滚动数组滚掉第一维。由于 \(|A_i|\) 递减,所以有 \(j\le \frac Si\),直接优化至 \(O(n^2\ln n)\) 级别。
代码
写的比较难看。
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define ADD(a, b) a = (a + b) % mod
const int N = 2e3 + 5, mod = 998244353;
int n, a[N], cnt[N], Lim[N], ans, f[2][N][N], g[N][N], s[N];
signed main(){
cin >> n;
for(int i = 1; i <= n; ++i) scanf("%lld", &a[i]), ++cnt[a[i]];
for(int i = 1; i <= n; ++i)
for(int j = 1; j <= n; ++j)
Lim[i] += min(i, cnt[j]);
for(int S = 1; S <= Lim[1]; ++S) f[1][S][S] = 1;
for(int i = 2; i <= n; ++i){
int id = (i & 1), _id = (id ^ 1);
for(int S = 0; S <= Lim[i - 1]; ++S)
for(int j = 0; j <= S / (i - 1); ++j)
g[S][j] = (g[S][j - 1] + f[_id][S][j]) % mod, f[_id][S][j] = 0;
for(int S = 0; S <= Lim[i]; ++S)
for(int j = 0; j <= S / i; ++j)
if(S - j <= Lim[i - 1] && j <= S - j)
f[id][S][j] = (g[S - j][min(S - j, (S - j) / (i - 1))] - g[S - j][min(j - 1, (S - j) / (i - 1))] + mod) % mod;
}
for(int i = 0; i <= n; ++i) ADD(ans, f[n & 1][n][i]);
cout << ans;
return 0;
}

浙公网安备 33010602011771号