[CTSC2018]青蕈领主

[CTSC2018]青蕈领主 

题解

首先,连续段要知道结论:

连续段要么不交,要么包含

所以是一棵树!每个位置的father是后面第一个包含它的

树形DP!

设dp[x],x为根的子树,(设管辖的区间长度为len,也即L[x]),用1~len的数填充,满足L的方案数

也就是,每个son内部合法,

给每个son分配标号区间,使得相邻儿子不会再接在一起

不会再接在一起?

 所以可以把每个儿子看成单独一个点,就划归成了:1,1,1,1,...len的方案数!

 

设f[i]表示,长度为i+1的1,1,1,1....i+1的序列的合法方案数。

$dp[x]=(\Pi dp[son])\times f[L[x]]$

归纳一下,可得$ans=\Pi f[|son(x)|]$,$|son(x)|$表示x的儿子个数

 

求f[n]?

考虑变成排列的逆!

(也就是把映射矩阵的横纵坐标意义交换)

这样,n+1成为了天然的分割点。

 

f[n-1]中,填入1,

要么之前合法。

要么1分开了一些东西

|son(x)|可以直接单调栈

 

注意,Poly每次clear需要重新resize

 

#include<bits/stdc++.h>
#define reg register int
#define il inline
#define fi first
#define se second
#define mk(a,b) make_pair(a,b)
#define numb (ch^'0')
#define pb push_back
#define solid const auto &
#define enter cout<<endl
#define pii pair<int,int>
using namespace std;
typedef long long ll;
template<class T>il void rd(T &x){
    char ch;x=0;bool fl=false;while(!isdigit(ch=getchar()))(ch=='-')&&(fl=true);
    for(x=numb;isdigit(ch=getchar());x=x*10+numb);(fl==true)&&(x=-x);}
template<class T>il void output(T x){if(x/10)output(x/10);putchar(x%10+'0');}
template<class T>il void ot(T x){if(x<0) putchar('-'),x=-x;output(x);putchar(' ');}
template<class T>il void prt(T a[],int st,int nd){for(reg i=st;i<=nd;++i) ot(a[i]);putchar('\n');}
namespace Modulo{
const int mod=998244353;
int ad(int x,int y){return (x+y)>=mod?x+y-mod:x+y;}
void inc(int &x,int y){x=ad(x,y);}
int sub(int x,int y){return ad(x,mod-y);}
int mul(int x,int y){return (ll)x*y%mod;}
void inc2(int &x,int y){x=mul(x,y);}
int qm(int x,int y=mod-2){int ret=1;while(y){if(y&1) ret=mul(x,ret);x=mul(x,x);y>>=1;}return ret;}
template<class ...Args>il int ad(const int a,const int b,const Args &...args) {return ad(ad(a,b),args...);}
template<class ...Args>il int mul(const int a,const int b,const Args &...args) {return mul(mul(a,b),args...);}
}  
using namespace Modulo;
namespace Miracle{
const int N=50000+5;
const int G=3;
const int GI=332748118;
int n,T;
int L[N];
struct Poly{
    vector<int>f;
    Poly(){f.clear();}
    il int &operator[](const int &x){assert(x<f.size());return f[x];}
    il void resize(int n){f.resize(n);}
    il int size(){return f.size();}
    il void clear(){f.clear();}
    il void out(){for(reg i=0;i<(int)f.size();++i) ot(f[i]);putchar('\n');}
}f;
int rev[8*N];
int init(int n){
    int m=0;
    for(m=1;m<n;m<<=1);
    for(reg i=0;i<m;++i){
        rev[i]=(rev[i>>1]>>1)|((i&1)?m>>1:0);
    }
    return m;
}
void NTT(Poly &f,int c){
    int n=f.size();
    for(reg i=0;i<n;++i){
        if(i<rev[i]) swap(f[i],f[rev[i]]);
    }
    for(reg p=2;p<=n;p<<=1){
        int gen;
        if(c==1) gen=qm(G,(mod-1)/p);
        else gen=qm(GI,(mod-1)/p);
        for(reg l=0;l<n;l+=p){
            int buf=1;
            for(reg i=l;i<l+p/2;++i){
                int tmp=mul(f[i+p/2],buf);
                f[i+p/2]=ad(f[i],mod-tmp);
                f[i]=ad(f[i],tmp);
                buf=mul(buf,gen);
            }
        }
    }
    if(c==-1){
        int iv=qm(n);
        for(reg i=0;i<n;++i){
            f[i]=mul(f[i],iv);
        }
    }
}
il Poly operator *(Poly F,Poly G){
    int n=init(F.size()+G.size()-1);
    F.resize(n);G.resize(n);
    NTT(F,1);NTT(G,1);
    for(reg i=0;i<n;++i) F[i]=mul(F[i],G[i]);
    NTT(F,-1);
    return F;
}
void divi(int l,int r){
    
    if(l==0&&r==1){
        f[0]=1;f[1]=2;return;
    }
    if(l==r){
        f[l]=ad(f[l],mod-mul(f[1],f[l-1],l-2));
        f[l]=ad(f[l],mul(l-1,f[l-1]));
        return;
    }
    int mid=(l+r)>>1;
    divi(l,mid);
    // cout<<" divi ---------------------------"<<l<<" "<<r<<endl;
    Poly F,G,K;
    if(l==0){
        // goto s;
        // cout<<"sol1---- "<<endl;
        F.resize(mid+1);
        G.resize(mid+1);
        for(reg i=1;i<=mid;++i){
            F[i]=f[i];
            G[i]=mul(f[i],i-1);
        }
        // F.out();
        // G.out();
        // cout<<"hahahaha "<<endl;
        F=F*G;
        // F.out();
        
        for(reg i=mid+1;i<=r;++i){
            f[i]=ad(f[i],F[i]);
        }
        // cout<<" over "<<endl;
    }else{
        
        // cout<<"sol2**** "<<endl;
        F.resize(mid-l+1);
        G.resize(r-l+1);
        for(reg i=0;i<=mid-l;++i){
            F[i]=f[i+l];
        }
        for(reg i=1;i<=r-l;++i){
            G[i]=mul(f[i],i-1);
        }
        // F.out();
        // G.out();
        // cout<<" mul "<<endl;
        K=F*G;
        // cout<<" K "<<endl;
        // K.out();

        for(reg i=mid+1;i<=r;++i){
            f[i]=ad(f[i],K[i-l]);
        }
        
        F.clear();G.clear();
        F.resize(mid-l+1);G.resize(r-l+1);

        // cout<<F.size()<<" len "<<mid-l+1<<endl;
        for(reg i=0;i<=mid-l;++i){
            F[i]=mul(f[i+l],i+l-1);
        }
        // cout<<" OK ? "<<endl;
        for(reg i=1;i<=r-l;++i){
            G[i]=f[i];
        }
        
        K=F*G;
        for(reg i=mid+1;i<=r;++i){
            f[i]=ad(f[i],K[i-l]);
        }
    }
    // s:;
    // cout<<" end "<<f[3]<<endl;
    divi(mid+1,r);
}
int sta[N],top;
int main(){
    rd(T);rd(n);
    int m=init(n+1);
    f.resize(m);
    
    divi(0,m-1);
    // f.out();

    while(T--){
        for(reg i=1;i<=n;++i) rd(L[i]);
        top=0;
        if(L[n]!=n) {
            puts("0");continue;
        }
        int ans=1;
        bool fl=true;
        for(reg i=1;i<=n;++i){
            int son=0;
            while(top&&sta[top]-L[sta[top]]+1>=i-L[i]+1) ++son,--top;
            // cout<<" ii "<<i<<" son "<<son<<endl;
            if(top){
                if(sta[top]>=i-L[i]+1) fl=false;
            }
            sta[++top]=i;
            ans=mul(ans,f[son]);
        }
        if(!fl) ans=0;
        printf("%d\n",ans);
    }
    return 0;
}

}
signed main(){
    Miracle::main();
    return 0;
}

/*
   Author: *Miracle*
*/

树形结构!

然后递归为子问题。

递归为子问题,把。。。看成。。。。

本质就是“缩点”找相似结构。或者归纳

 

转载于:https://www.cnblogs.com/Miracevin/p/11019351.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值