给定一个长 ,值域 的序列 ,求有多少个 的排列 ,满足 的 不构成 的排列。对 取模。
。
为什么有一大堆题解都没提到第二次容斥?注意力太强了。
钦定 个区间的一个区间子集 使得 , 构成排列,容斥系数为 。
若两个区间 和 有交但不是包含关系,不妨假设 ,则 构成排列,同理 和 也构成排列。并且这是双射的,若 、 和 均构成排列,则 和 构成排列。
那么这两个区间可以拆成三个区间。
所以现在 需要满足内部区间包含或相离,套路地使用树形结构描述,每个区间对应一个节点,其儿子是被之包含的若干个极长区间。则每个点的方案数是区间内散点的数量的阶乘。
问题在于容斥系数,若一个点 有一些儿子区间是相邻的,即 ,则容斥系数不显然。因为我们要计算的是在 中选若干个(假设选了 个)满足 的子区间 使得它们不断拆开能恰好得到 的所有儿子区间,所有这样方案的 的和,不妨称这些区间为原始区间。
考虑继续容斥,对于每个连续段分别考虑,我们的目标是将容斥系数放到每个儿子区间上。假设当前连续段是 ,显然仅需满足每个儿子区间都被某个原始区间包含,且所有原始区间的左端点 ,右端点 。
那么钦定 个儿子区间不被任何原始区间包含,容斥系数为 。考虑剩下任选的方案数,不难发现,若没有任何合法的原始区间可选,则方案数为 ,否则方案数就是 ,其中 为可选的原始区间个数。
而由于原始区间是形如 这样的,故这相当于要求除去钦定的儿子区间外,剩余的儿子区间都满足 ,而钦定的儿子区间则每个贡献 的系数,这对于多个连续段的情况依然是成立的。
那么考虑对于一个儿子区间 ,若 则其会贡献 的系数(钦定是 ,未被钦定则是 ),故 的所有儿子区间都必须满足 ,并会带来 的系数。
那么考虑区间 dp,设 表示区间 的子树的答案; 表示区间 内且散点个数为 时儿子的 的乘积(计算了容斥系数但未计算 的系数),转移 即可。
代码如下:
#include <iostream>
#include <cstdio>
using namespace std;
const int S=205;
#define p 1000000007
int fra[S];
int n,a[S];
int g[S][S][S],f[S][S];
inline void add(int &x,int y)
{
x+=y;
if(x>=p) x-=p;
}
int main()
{
fra[0]=1;
for(int i=1;i<=S-3;i++) fra[i]=1ll*fra[i-1]*i%p;
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
if(a[1]==n) return puts("0"),0;
for(int i=0;i<=n;i++) g[i+1][i][0]=1;
for(int len=1;len<=n;len++)
{
for(int l=1;l<=n-len+1;l++)
{
int r=l+len-1;
for(int k=0;k<=len;k++)
{
for(int x=r;x>=l+1;x--)
if(r<=a[x])
add(g[l][r][k],
p-1ll*g[l][x-1][k]*f[x][r]%p);
if(k>0) add(g[l][r][k],g[l][r-1][k-1]);
}
for(int k=0;k<=len;k++)
add(f[l][r],1ll*fra[k]*g[l][r][k]%p);
if(r<=a[l]) add(g[l][r][0],p-f[l][r]);
}
}
printf("%d\n",f[1][n]);
return 0;
}