peiwenjun's blog 没有知识的荒原

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\) ,方案数为:

\[\sum_{j=0}^k j(k-1)!\cdot\binom{a_1}{j-b_i}\binom{a_2}{k-j-b_2} \]

做一些平凡的化简可以得到:

\[b_1(k-1)!\binom{a_1+a_2}{k-b_1-b_2}+a_1(k-1)!\binom{a_1+a_2-1}{k-b_1-b_2-1} \]

上面计算的是 \(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;
}

posted on 2023-06-22 11:51  peiwenjun  阅读(6)  评论(0)    收藏  举报

导航