题解 P7278 【纯洁憧憬】
本文同步发表于我的博客:https://www.alpha1022.me/articles/lg-7278.htm。
首先正难则反,先算不存在的方案数再用
考虑这个排列的析合树。
若根为合点,则儿子序列上的任何连续子序列都将是一个连续段。
故此时条件等价于第一个儿子和最后一个儿子的大小都不小于
考虑设
这样的排列可以作为其中一个儿子。
有递推式
此部分复杂度为
枚举第一个和最后一个儿子的大小,从而这种情况的答案为
这可以直接
若根为析点,则只需要保证每个儿子的大小不超过
设
这样的排列可以作为根的儿子序列,而儿子内部可以任意排列。
考虑递推
可以用所有方案减去根为合点的方案再减去根为析点但儿子个数不超过
其中有初值
设
则有递推式
求得
于是这种情况的答案为
综上,总答案为
复杂度
代码:
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 400;
const int mod = 1e9 + 7;
int n,k;
int fac[N + 5];
int f[N + 5],g[N + 5];
int F[N + 5][N + 5],G[N + 5][N + 5];
int ans;
int main()
{
scanf("%d%d",&n,&k),fac[0] = 1;
for(register int i = 1;i <= n;++i)
fac[i] = (long long)fac[i - 1] * i % mod;
for(register int i = 1;i <= n;++i)
{
g[i] = fac[i];
for(register int j = 1;j < i;++j)
g[i] = (g[i] - (long long)g[j] * fac[i - j] % mod + mod) % mod;
}
G[0][0] = 1;
for(register int i = 1;i <= n;++i)
for(register int j = 1;j <= i;++j)
for(register int x = 1;x <= i;++x)
G[i][j] = (G[i][j] + (long long)G[i - x][j - 1] * fac[x]) % mod;
f[1] = 1,f[2] = 2;
for(register int i = 3;i <= n;++i)
{
f[i] = fac[i];
for(register int j = 1;j < i;++j)
f[i] = (f[i] - 2LL * g[j] * fac[i - j] % mod + mod) % mod;
for(register int j = 4;j < i;++j)
f[i] = (f[i] - (long long)f[j] * G[i][j] % mod + mod) % mod;
}
F[0][0] = 1;
for(register int i = 1;i <= n;++i)
for(register int j = 1;j <= i;++j)
for(register int x = 1;x <= min(i,k);++x)
F[i][j] = (F[i][j] + (long long)F[i - x][j - 1] * fac[x]) % mod;
for(register int i = n - k;i <= k;++i)
for(register int j = n - k;j <= n - i;++j)
ans = (ans + 2LL * g[i] * g[j] % mod * fac[n - i - j]) % mod;
for(register int i = 4;i <= n;++i)
ans = (ans + (long long)f[i] * F[n][i]) % mod;
ans = (fac[n] - ans + mod) % mod;
printf("%d\n",ans);
}