peiwenjun's blog 没有知识的荒原

P4566 [CTSC2018] 青蕈领主 题解

题目描述

对于一个排列 \(p\) ,记 \(l_i\) 为满足 \(p_{i-l_i+1},\cdots,p_i\) 在数轴上连续的最大 \(l_i\) (显然有\(1\le l_i\le i\))。

\(T\) 组询问,给定数组 \(l_i\) ,求有多少个排列 \(p\) 符合条件。

数据范围

  • \(1\le T\le 100,1\le n\le 50000\)
  • \(1\le l_i\le i\)

时间限制 \(\texttt{3s}\) ,空间限制 \(\texttt{500MB}\)

分析

\(\texttt{Key observation}\)区间 \([i-l_i+1,i]\) 要么不交,要么包含!

证明:数轴上两个相交连续区间的并集仍是连续区间。

更进一步,所有 \([i-l_i+1,i]\) 的区间可以根据包含关系画成一棵树,并且根节点为 \([1,n]\)

至此可以给出无解条件:区间 \([i-l_i+1,i]\) 存在相交关系,或者 \(l_n\neq n\)


\(f_k\) 为长为 \(k+1\)\(l\) 数组 1 ... 1 k+1 的答案。

对于节点 \(u\) ,假设它有 \(deg_u\) 个子节点,这一步我们只需决策这 \(deg_u\) 个区间的相对位置关系,使得每个区间都是极长连续区间。

显然不同步的决策相互独立,因此 \(ans=\prod_{i=1}^nf_{deg_i}\)

\(deg\) 可以用单调栈求出,接下来目标变为求 \(f_n\)


递推,在一个长为 \(n\) 的排列 \(p\) 中插入 \(n+1\)

如果 \(p\) 合法,只需保证 \(n+1\) 没有插在 \(n\) 旁边即可,方案数为 \((n-1)f_{n-1}\)

如果 \(p\) 不合法,并且插入 \(n+1\) 后变得合法,那么 \(p\) 中长度 \(\gt 1\) 的极长连续区间(不考虑 \([1,n]\),下同)恰好只能有一个,并且 \(n+1\) 必须插在这个区间内部。

假设这个区间长度为 \(i\)\(1\lt i\le n-2\)),先将它缩成一个点,我们会得到一个长为 \(n-i+1\) ,并且除末尾元素外 \(l\) 全为 \(1\) 的排列 \(q\) ,这样的排列有 \(f_{n-i}\) 个。

接下来要做的事情是在值域 \([1,n-i+1]\) 上找一个点 \(x\) ,将其扩展成长为 \(i\) 的区间 \([x,x+i-1]\) ,然后插入 \(n+1\)

  • 首先 \(x\) 不能是最大值 \(n-i+1\) ,否则 \([x,x+i-1]\cup\{n+1\}\) 仍是一个连续区间。
  • 其次 \(x\) 不能是最后一个点 \(q_{n-i+1}\) ,因为我们已经默认前提删去 \(n+1\) 后排列不合法。

注意 \(q_{n-i+1}\neq n-i+1\) ,否则在 \(q\) 中删去最后一个值,\([1,n-i]\) 也是极长区间,与 \(q\) 的定义矛盾。

因此 \(x\)\((n-i+1)-2=n-i-1\) 种取法,接下来考虑 \([x,x+i-1]\cup\{n+1\}\) 的排列方式。

重标号后等价于将 \([1,i+1]\) 进行排列,使得 \(l\) 数组为 1 ... 1 i+1 ,方案数为 \(f_i\)

综上所述:

\[f_n=(n-1)f_{n-1}+\sum_{i=2}^{n-2}(n-i-1)f_if_{n-i}\\ \]

初始值 \(f_0=1,f_1=2\) ,具体参见 OEIS A090753


半在线卷积?分治 \(\texttt{NTT}\) 准没错,但是具体过程仍然非常复杂。

模板题中 \(f_i=\sum f_jg_{i-j}\) ,其中 \(g\) 已知;但是本题 \(g_i=(i-1)f_i\) 仍然依赖 \(f\)

\(\texttt{cdq}\) 分治的本质是 \(f[l\sim mid]\times g[1\sim r-l]\to f[mid+1\sim r]\) ,如果 \(r-l\gt mid\)\(g\) 数组未知,无法进行上述转移。

\(\texttt{Key observation}\)\(r-l\gt mid\iff l=1\) ,即每次二分都往左走。

此时我们无法得到整个 \(g\) 数组,所以只能 \(f[l\sim mid]\times g[l\sim mid]\)

因此我们必须在 \(l\neq 1\) 时打补丁,除了常规的 \(f[l\sim mid]\times g[1\sim r-l]\) 以外,还要补上 \(f\) 下标小于 \(g\) 下标时的贡献,即 \(f[1\sim r-l]\times g[l\sim mid]\) (注意此时 \(r-l\le l\) 一定成立)。

时间复杂度\(O(Tn+n\log^2n)\)

#include<bits/stdc++.h>
using namespace std;
const int maxn=1<<17,mod=998244353;
int n,t,x;
int f[maxn],u[maxn],st[maxn];
namespace Poly
{
    int p[maxn],q[maxn],r[maxn],w[maxn];
    int qpow(int a,int k)
    {
        int res=1;
        for(;k;a=1ll*a*a%mod,k>>=1) if(k&1) res=1ll*res*a%mod;
        return res;
    }
    int add(int x,int y)
    {
        if((x+=y)>=mod) x-=mod;
        return x;
    }
    int dec(int x,int y)
    {
        if((x-=y)<0) x+=mod;
        return x;
    }
    int extend(int n)
    {
        return n!=1?1<<(__lg(n-1)+1):1;
    }
    void get_r(int n)
    {
        for(int i=0;i<n;i++) r[i]=(r[i>>1]>>1)|(i&1?n>>1:0);
    }
    void init(int n=maxn)
    {
        for(int k=2,m=1;k<=n;k<<=1,m<<=1)
        {
            w[m]=1;
            for(int i=m+1,x=qpow(3,(mod-1)/k);i<k;i++) w[i]=1ll*w[i-1]*x%mod;
        }
    }
    void print(int *a,int n)
    {
        for(int i=0;i<n;i++) printf("%d%c",a[i]," \n"[i==n-1]);
    }
    void ntt(int *a,int n,int op)
    {
        for(int i=0;i<n;i++) if(i<r[i]) swap(a[i],a[r[i]]);
        for(int k=2,m=1;k<=n;k<<=1,m<<=1)
            for(int i=0;i<n;i+=k)
                for(int j=i,*x=w+m;j<i+m;j++,x++)
                {
                    int v=1ll*a[j+m]**x%mod;
                    a[j+m]=dec(a[j],v),a[j]=add(a[j],v);
                }
        if(op==-1)
        {
            reverse(a+1,a+n);
            for(int i=0,v=qpow(n,mod-2);i<n;i++) a[i]=1ll*a[i]*v%mod;
        }
    }
    void tran(int l1,int r1,int l2,int r2,int l3,int r3)
    {
        int n=r1-l1+1,m=r2-l2+1,len=extend(n+m-1);
        for(int i=0;i<len;i++) p[i]=i<n?f[i+l1]:0,q[i]=i<m?(i+l2-1ll)*f[i+l2]%mod:0;
        get_r(len),ntt(p,len,1),ntt(q,len,1);
        for(int i=0;i<len;i++) p[i]=1ll*p[i]*q[i]%mod;
        ntt(p,len,-1);
        for(int i=max(l3,l1+l2);i<=min(r3,r1+r2);i++) f[i]=add(f[i],p[i-l1-l2]);
    }
}
using Poly::tran;
void cdq(int l,int r)
{
    if(l==r) return f[l]=(f[l]+(l-1ll)*f[l-1])%mod,void();
    int mid=(l+r)>>1;
    cdq(l,mid);
    if(l==2) tran(l,mid,l,mid,mid+1,r);
    else tran(l,mid,2,r-l,mid+1,r),tran(2,r-l,l,mid,mid+1,r);
    cdq(mid+1,r);
}
int work()
{
    for(int i=1;i<=n;i++) scanf("%d",&x),u[i]=i-x+1;
    if(u[n]!=1) return 0;
    int res=1;
    for(int i=1,top=0;i<=n;i++)
    {
        int cnt=0;
        while(top&&u[st[top]]>=u[i]) cnt++,top--;
        if(top&&u[st[top]]<u[i]&&u[i]<=st[top]) return 0;
        st[++top]=i,res=1ll*res*f[cnt]%mod;
    }
    return res;
}
int main()
{
    scanf("%d%d",&t,&n),Poly::init();
    f[0]=1,f[1]=2,cdq(2,n);
    while(t--) printf("%d\n",work());
    return 0;
}

posted on 2025-01-24 17:59  peiwenjun  阅读(25)  评论(0)    收藏  举报

导航