分治FFT“自己卷自己”形/[CTSC2018]青蕈领主

众所周知分治FFT可以用来解决这样的问题

\[f_i=\sum_{j=1}^{i-1}f_ig_{i-j} \]

其中 \(G(x)\) 是一个已知的多项式
但是有的时候我们会遇到这样的情况,\(G(x)\) 的系数依赖与 \(F(x)\)\(1\sim i-1\) 次项系数,这个时候直接分治FFT就不太行了
这里讨论的就是这种分治FFT“自己卷自己”形的一般性的处理方法
这里讨论的是

\[f_i=\sum_{j=1}^{i-1}f_if_{i-j} \]

\(G(x)=F(x)\)显然另外的情况都可以表示成这个样子。

在这种情况下,不能直接运用之前的做法的一个原因是,在 \(f[l,mid]\)\(f[mid+1,r]\) 转移的时候,实际上需要的 \(g[mid+1,r]\) 这一部分我们还没有。
考虑这个问题什么时候会出现,也就是 \(r-l\geq l\) 的时候。根据分治的过程我们不难发现这个等价于 \(l=1\)
所以在 \(l>1\) 的时候我们应该是按之前的做就可以
但是 \(l=1\) 的时候因为不知道有半部分,所以只能 \([l,mid]\)\([l,mid]\) 进行转移,在后面我们再把转移补偿回来。
所以这个时候再 \(l>1\) 的时候需要相应的进行改变
具体的流程是:

  • \(l=1\),直接 \([l,mid]\)\([l,mid]\) 卷,累加到 \(f[mid+1,r]\)
  • \(l\neq 1\),先 \(f[l,mid]\)\(g[l,r]\) 卷,累加到 \(f[mid+1,r]\) 上,再 \(g[l,mid]\)\(f[l,r]\) 卷,累加到 \(f[mid+1,r]\)

这个不太好理解,我们可以想像对于 \([x+1,r]\),他相当于是 \(f_i\)\(g_j\) 转移过来的
那么有可能是 \(i,j\) 都在 \([l,x]\),也有可能是 \(i\)\(j\) 有一个在 \([x+1,r]\) 里。
上面两类可以理解为对应了两种转移。

下面是一道例题

P4566 [CTSC2018]青蕈领主

首先判断无解的情况,\(a_n\neq n\) 或者有两个区间相交
否则一定有解,为什么,猜。
对于每一个区间,他会直接包含一些区间,并且这些直接包含的区间并起来加上右端点就是这个区间
所以实际上可以表示成一个树形结构,每一个子树里的编号都是连续的
现在要求一个点的两个儿子之间不能是连续的,所以对于每一个点,可以把这个点和他们的儿子按照大小关系重新编号一下,相当于是求一个 \(L_i\)\(1,1,1,\cdots x+1\) 的排列的个数(\(x\) 是儿子的个数)
考虑如何解决这个问题,事实上,直接解决是困难的。
但是我们注意到他的反排列 \(a_{-1}\) 满足 \(a^{-1}_{a_i}=i\),如果 \(a_i\)\([L,R]\) 上是连续的,那么 \(a^{-1}_i\) 一定在 \([\alpha,\beta]\) 上是连续的。
如果 \(a_i\)\(L\)\(1,1,1,1,x-1\) 的话,那么 \(a^{-1}\) 的长度大于1的连续区间只能有一个,并且必须包含这个区间的最大值。
并且反排列和原排列是一一对应的
我们现在用 \(f_i\) 表示长度为 \(i+1\)(注意长度) 的,连续区间长度大于1的连续区间包含区间最大值的排列的方案数
考虑从 \(i-1\)\(i\) 的转移。

  • 如果 \(i-1\) 构成的排列合法:
    我们考虑最大值除了在 \(i\) 两边的位置以外都可以插进去,所以有 \(i-1\) 种选法,转移量就是 \((i-1)f_{i-1}\)
  • 如果 \(i-1\) 构成的排列不合法:
    事实上,这也代表着这个排列包含最大值的最长连续区间就是 \(1\)\(i\)
    这个时候 \(i-1\) 构成的排列只有可能有一个不包含 \(i\) 的连续区间,现在我们要插入一个最大值 \(i+1\) 让他不合法。
    设这个连续的区间(值域)是 \([x,x+l-1]\),注意到,对于 \(i+1\)\([x,x+l-1]\),把他们重新标号一下实际上就是 \(l+1\) 的长度的 \(f\) ,即 \(f_l\)
    考虑 \(x,l\) 应当满足的条件,由 \(x+l-1<i\) 可知 \(l\leq i-2,x\)\(i-l-1\) 种选法
    我们把 \([x,x+l-1]\) 看成一个点,继续和剩下的 \(i-l\) 个位置排序,这样转移量就是 \(f_{i-l}\)
    这样我们就得到了最终的转移

\[f_i=(i-1)f_{i-1}+\sum_{j=1}^{n-2}(j-1)f_jf_{i-j} \]

\(f_0=1,f_1=2\)
这个式子就和上面那个式子非常的像了,为了方便我们还可以把他改写一下

\[f_i=-(i-3)f_{i-1}+\sum_{j=1}^{n-1}(j-1)f_jf_{i-j} \]

现在就是完全一样了

点击查看代码
#include <bits/stdc++.h>

using namespace std;

# define Rep(i,a,b) for(int i=a;i<=b;i++)
# define _Rep(i,a,b) for(int i=a;i>=b;i--)
# define RepG(i,u) for(int i=head[u];~i;i=e[i].next)

const int N=1<<17;
const int mod=998244353;

typedef long long ll;
typedef double db;

# define chkmax(a,b) a=max(a,b)
# define chkmin(a,b) a=min(a,b)
# define PII pair<int,int>
# define mkp make_pair

template<typename T> void read(T &x){
    x=0;int f=1;
    char c=getchar();
    for(;!isdigit(c);c=getchar())if(c=='-')f=-1;
    for(;isdigit(c);c=getchar())x=(x<<1)+(x<<3)+c-'0';
    x*=f;
}

int t,n;
int a[N];
int f[N],g[N];
int A[N],B[N];
int pos[N],tmp[N];
int stk[N],top;

int inc(int x,int y){
    return x+y>=mod?x+y-mod:x+y;
}

int dec(int x,int y){
    return x-y<0?x-y+mod:x-y;
}

int Qpow(int base,int ind){
    int res=1;
    while(ind){
        if(ind&1)res=1ll*res*base%mod;
        base=1ll*base*base%mod;
        ind>>=1;
    }
    return res;
}

void init(int limit){
    for(int i=1;i<limit;i<<=1)for(int j=0;j<i;j++)pos[i+j]=pos[j]+limit/i/2;
}

void NTT(int* A,int limit,int opt){
    for(int i=0;i<limit;i++)tmp[pos[i]]=A[i];
    for(int i=0;i<limit;i++)A[i]=tmp[i];
    for(int i=1;i<limit;i<<=1){
        int len=i<<1;
        int g0=Qpow(opt==1?114514:137043501,(mod-1)/len);
        for(int j=0;j<limit;j+=len)
            for(int k=0,g=1;k<i;k++,g=1ll*g*g0%mod){
                int x=A[j+k],y=1ll*A[j+k+i]*g%mod;
                A[j+k]=inc(x,y);
                A[j+k+i]=dec(x,y);
            }
    }
    if(opt==-1){
        int inv=Qpow(limit,mod-2);
        for(int i=0;i<limit;i++)A[i]=1ll*A[i]*inv%mod;
    }
}

void kakeru(int* A,int* B,int limit){
    NTT(A,limit,1),NTT(B,limit,1);
    for(int i=0;i<limit;i++)A[i]=1ll*A[i]*B[i]%mod;
    NTT(A,limit,-1);
}

void solve(int l,int r){
    if(l==r){
        if(l!=1)f[l]=dec(f[l],1ll*dec(l,3)*f[l-1]%mod);
        g[l]=1ll*f[l]*(l-1)%mod;
        return;
    }
    int mid=l+r>>1;
    solve(l,mid);
    if(l==1){
        int limit=1;
        while(limit<=2*mid)limit<<=1;
        Rep(i,1,mid)A[i]=f[i],B[i]=g[i];
        init(limit);
        kakeru(A,B,limit);
        Rep(i,mid+1,r)f[i]=inc(f[i],A[i]);
        for(int i=0;i<limit;i++)A[i]=B[i]=0;
    }
    else{
        int limit=1;
        while(limit<=mid-l+1+r-l+1)limit<<=1;
        init(limit);
        Rep(i,l,mid)A[i-l]=f[i];
        Rep(i,1,r-l)B[i]=g[i];
        kakeru(A,B,limit);
        Rep(i,mid+1,r)f[i]=inc(f[i],A[i-l]);
        for(int i=0;i<limit;i++)A[i]=B[i]=0;
        Rep(i,l,mid)A[i-l]=g[i];
        Rep(i,1,r-l)B[i]=f[i];
        kakeru(A,B,limit);
        Rep(i,mid+1,r)f[i]=inc(f[i],A[i-l]);
        for(int i=0;i<limit;i++)A[i]=B[i]=0;
    }
    solve(mid+1,r);
}

int main()
{
    # ifdef YuukiYumesaki
    freopen("testdata.in","r",stdin);
    freopen("test1.out","w",stdout);
    # endif
    read(t),read(n);
    f[0]=1,f[1]=2;
    solve(1,n);
    while(t--){
        int ans=1;
        Rep(i,1,n)read(a[i]);
        if(a[n]!=n){puts("0");continue;}
        top=0;
        bool flag=true;
        Rep(i,1,n){
            int x=0;
            while(top&&stk[top]-a[stk[top]]>=i-a[i])top--,x++;
            ans=1ll*ans*f[x]%mod;
            if(top&&stk[top]!=i-a[i]){flag=false;break;}
            stk[++top]=i;
        }
        if(!flag)puts("0");
        else printf("%d\n",ans);
    }
    return 0;
}
posted @ 2021-12-17 21:05  YuukiYumesaki  阅读(81)  评论(0编辑  收藏  举报