CF1750G Doping 题解
题目描述
对于一个长为 \(n\) 的排列 \(p\) ,定义 \(f(p)\) 为将 \(p\) 划分为公差为 \(1\) 的等差数列的段数。
给定长为 \(n\) 的排列 \(p\) ,对 \(\forall 1\le k\le n\) ,求有多少个字典序小于 \(p\) 的排列 \(p'\) ,满足 \(f(p')=k\) ,对给定整数 \(m\) (不一定是素数)取模。
数据范围
- \(1\le n\le 2000,10\le m\le 10^9\) 。
时间限制 \(\texttt{3s}\) ,空间限制 \(\texttt{512MB}\) 。
分析
第一步二项式反演是容易的,问题转化为计算钦定 \(k\) 个连续段的排列数,记为 \(g_k\) 。
枚举 \(\texttt{lcp}(p',p)=i-1\) ,字典序限制转化为 \(p'_i\lt p_i\) 。
将 \([1,i-1]\) 和 \([i,n]\) 的连续段分开考虑。
先考虑后面的连续段,记 \(S=\{p_i,\cdots,p_n\}\) 为未填的数构成的集合。
记 \(S\) 中 \(\lt p_i\) 的数中形如 \((x-1,x)\) 的数对有 \(a_1\) 个,值域上的连续段有 \(b_1\) 个。
记 \(S\) 中 \(\ge p_i\) 的数中形如 \((x-1,x)\) 的数对有 \(a_2\) 个,值域上的连续段有 \(b_2\) 个。
注意 \(a_1+b_1\) 为 \(S\) 中 \(\lt p_i\) 的数的个数, \(a_2+b_2\) 为 \(S\) 中 \(\ge p_i\) 的数的个数。
我们认为一个连续段 \(\lt p_i\) 当且仅当其开头元素 \(\lt p_i\) ,注意两部分我们都是在连续段开头位置计入连续段的数量。
如果要将 \(S\) 划分为 \(k\) 个连续段。枚举 \(\lt p_i\) 的连续段有 \(j\) 个, \(\ge p_i\) 的连续段有 \(k-j\) 个,限制第一个连续段必须 \(\lt p_i\) ,方案数为:
做一些平凡的化简可以得到:
上面计算的是 \(p'_i\) 作为连续段开头的贡献,如果 \(p_{i-1}-1\in S\) ,那么 \(p'_i\) 也可以不作为连续段开头,方案数为 \((k-1)!\binom{a_1+a_2}{k-b_1-b_2}\) 。
枚举 \(i\) ,我们可以在 \(\mathcal O(n)\) 的时间内计算下标 \([i,n]\) 的贡献。
再考虑前面的连续段。
注意 \(p'_1\sim p'_{i-1}\) 是确定的,我们只需要 \(\texttt{dp}\) 每个位置是否被钦定为连续段开头。
具体的, \(h_{i,j}\) 表示考虑下标 \([1,i]\) ,有 \(j\) 个连续段的方案数,转移是容易的。
这时问题出现了, \(\texttt{lcp}=i-1\) 的情况我们需要将两部分的数组做卷积再贡献给 \(g\) ,由于模数任意,所以数组卷积是 \(\mathcal O(n^2)\) 的,算上外层枚举 \(k\) 的代价,时间复杂度 \(\mathcal O(n^3)\) 。
考虑在处理 \([1,i-1]\) 下标的贡献时做一点手脚。
注意到我们不一定需要从前往后 \(\texttt{dp}\),从后往前 \(\texttt{dp}\) 本质相同。
修改一下 \(h_{i,j}\) 的定义:考虑下标 \([i,n]\) ,钦定了 \(j\) 个连续段开头的方案数。
从大到小枚举 \(i\),做完 \(i\to i-1\) 的转移后,加入 \(\texttt{lcp}=i-1\) 的贡献。
本质上是将 \(n\) 个数组卷积放在一起转移,时间复杂度 \(\mathcal O(n^2)\) 。
#include<bits/stdc++.h>
using namespace std;
const int maxn=2005;
int n,mod;
int f[maxn],p[maxn],fac[maxn];
int c[maxn][maxn],h[maxn][maxn];
bool vis[maxn];
inline int add(int x,int y)
{
if((x+=y)>=mod) x-=mod;
return x;
}
void init(int n)
{
fac[0]=1;
for(int i=1;i<=n;i++) fac[i]=1ll*fac[i-1]*i%mod;
for(int i=0;i<=n;i++) c[i][0]=1;
for(int i=1;i<=n;i++)
for(int j=1;j<=i;j++)
c[i][j]=add(c[i-1][j-1],c[i-1][j]);
}
int main()
{
scanf("%d%d",&n,&mod),p[0]=-1,init(n);
for(int i=1;i<=n;i++) scanf("%d",&p[i]);
for(int i=n;i>=1;i--)
{
for(int j=0;j<=n;j++) h[i][j]=add(j?h[i+1][j-1]:0,p[i]==p[i-1]+1?h[i+1][j]:0);
vis[p[i]]=1;
int a1=0,b1=0,a2=0,b2=0;
for(int j=1;j<=n;j++)
{
if(!vis[j]) continue;
if(j<p[i]) vis[j-1]?a1++:b1++;
else vis[j-1]?a2++:b2++;
}
for(int j=b1+b2;j<=a1+b1+a2+b2;j++)
{
int cur=(1ll*b1*c[a1+a2][j-b1-b2]+(a1+a2&&j-b1-b2?1ll*a1*c[a1+a2-1][j-b1-b2-1]:0))%mod;
h[i][j]=(h[i][j]+1ll*cur*fac[j-1])%mod;
if(vis[p[i-1]+1]&&p[i-1]+1<p[i]) h[i][j-1]=(h[i][j-1]+1ll*c[a1+a2][j-b1-b2]*fac[j-1])%mod;
}
}
for(int i=1;i<=n;i++)
{
f[i]=h[1][i];
for(int j=1;j<i;j++) f[i]=(f[i]-1ll*c[n-j][i-j]*f[j])%mod;
printf("%d ",(f[i]+mod)%mod);
}
putchar('\n');
return 0;
}
本文来自博客园,作者:peiwenjun,转载请注明原文链接:https://www.cnblogs.com/peiwenjun/p/17497645.html
浙公网安备 33010602011771号