一种奇妙的块状DP
这种 \(DP\) 可以用来求一种合法序列的方案数
介绍
设 \(f_{i,j}\) 为前 \(i\) 个数分成 \(j\) 个合法序列的方案数
为了转移方便, 避免讨论, 我们一般会进行排序, 让他拥有一些性质
转移一般有三种
-
合并两个序列 \(f[i][j] = f[i - 1][j + 1] * j + f[i][j]\)
-
新开一个序列 \(f[i][j] = f[i][j] + f[i - 1][j - 1] * j\)
-
在一个序列中新加入元素 \(f[i][j] = f[i][j] + f[i - 1][j] * j\)
例题
一个合法序列必须得是波浪形状的, 且开头和结尾必须是 \(s\) 和 \(t\)
我们先从小到大排序, 然后可以发现当前的数比前边的数都大, 于是可以合并, 形成一个新的合法序列
也可以新开一个序列
但是对于加入元素就不可以了
对于 \(s\) 和 \(t\) 的限制, 只需要关注新开序列开在开头和结尾的限制就可以了
f[1][1] = 1;
for (ll i = 2; i <= n; i++)
for (ll j = 1; j <= i; j++){
if (i == s || i == t) f[i][j] = (f[i - 1][j - 1] + f[i - 1][j]) % mod;
else f[i][j] = (f[i - 1][j + 1] * j % mod + f[i - 1][j - 1] * (j - (i > s ? 1 : 0) - (i > t ? 1 : 0))) % mod;
}
printf("%lld", f[n][1]);
大意:
现在有一张 \(2n\) 个点的二分图,左边 \(n\) 个点,右边 \(n\) 个点。其中左边第 \(i\) 个点与右边 \(1 - a_i\) 个点有边。
你需要求出图中总共有多少个不同的简单环。简单环在这里指一个点只被经过至多一次的环。
答案对 \({10}^9 + 7\) 取模。
首先破环成链
然后我们观察到一个合法的链类似于上方的波浪性, 然后再通过一个点合并成环
所以我们先排序, 发现随着枚举左边的点一定包含之前所有的右部点
对于右部点可以新增一个序列
对于左部点可以合并两个序列, 同时把一个链合并成一个环
不一样的地方是这次合并不是相邻的两个序列, 而是任意两个, 所以需要 \(j * (j - 1)\)
因为一条链首尾不同我们视为是不同的, 所以一个环被算了两次, 最后除于 2 即可
两个点不算作一个环, 所以要减去
scanf("%lld", &n);
for (ll i = 1; i <= n; i++) scanf("%lld", a + i), ans = (ans - a[i] + mod) % mod;
std::sort(a + 1, a + n + 1);
f[0][0] = 1;
for (ll i = 1; i <= n; i++) {
for (ll j = a[i - 1] + 1; j <= a[i]; j++)
for (ll k = j; k >= 1; k--) f[i - 1][k] = (f[i - 1][k - 1] + f[i - 1][k]) % mod;
ans = (ans + f[i - 1][1]) % mod;
for (ll j = 0; j <= a[i]; j++)
f[i][j] = (f[i - 1][j + 1] * j % mod * (j + 1) % mod + f[i - 1][j]) % mod;
}
printf("%lld", ans * quickly_pow(2, mod - 2) % mod);