• HDU5279 YJC plays Minecraft (CDQ+NTT)


    HDU-5279(CDQ+NTT)

    本质其实是要求\(n\)个点森林数量\(dp_n\)\(n\)个点森林并且\(1,n\)在同一连通块的数量\(f_n\)

    总方案就是\(\Pi dp_{a_i}\cdot 2^n-\Pi f_{a_i}\)

    就是减去所有环都连着,并且\(1,a_i\)连着的方案数

    我们知道\(n\)个点树的数量是\(n^{n-2}\)(Prufer序列)

    枚举\(1\)号点所在树的大小为\(j\),则\(dp_i=dp_{i-j}\cdot j^{j-2}\cdot C(i-1,j-1)\)

    可以看到是一个与差值有关的转移,可以通过\(CDQ+NTT\)优化

    然后就是求\(f_i\),其实就是枚举\(1,i\)号点所在树的大小为\(j\)\(f_i=dp_{i-j}\cdot C(i-2,j-2) \cdot j^{j-2}\),可以直接\(NTT\)解决

    #include<bits/stdc++.h>
    using namespace std;
    
    #define reg register
    typedef long long ll;
    #define rep(i,a,b) for(reg int i=a,i##end=b;i<=i##end;++i)
    #define drep(i,a,b) for(reg int i=a,i##end=b;i>=i##end;--i)
    
    template <class T> inline void cmin(T &a,T b){ ((a>b)&&(a=b)); }
    template <class T> inline void cmax(T &a,T b){ ((a<b)&&(a=b)); }
    
    char IO;
    int rd(){
    	int s=0,f=0;
    	while(!isdigit(IO=getchar())) if(IO=='-') f=1;
    	do s=(s<<1)+(s<<3)+(IO^'0');
    	while(isdigit(IO=getchar()));
    	return f?-s:s;
    }
    
    const ll N=(1<<18)|4,P=998244353;
    
    int n=1e5;
    ll Fac[N],Inv[N];
    ll Tr[N];
    ll qpow(ll x,ll k){
    	ll res=1;
    	for(;k;k>>=1,x=x*x%P) if(k&1) res=res*x%P;
    	return res;
    }
    ll dp[N],f[N],A[N],B[N];
    int rev[N];
    
    void NTT(int n,ll *a,int f) {
    	rep(i,0,n-1) if(rev[i]<i) swap(a[i],a[rev[i]]);
    	for(reg int i=1;i<n;i<<=1) {
    		ll w=qpow(f==1?3:(P+1)/3,(P-1)/i/2);
    		for(reg int l=0;l<n;l+=2*i) {
    			ll e=1;
    			for(reg int j=l;j<l+i;++j,e=e*w%P) {
    				ll t=a[j+i]*e%P;
    				a[j+i]=a[j]-t;
    				a[j]=a[j]+t;
    				(a[j+i]<0&&(a[j+i]+=P));
    				(a[j]>=P&&(a[j]-=P));
    			}
    		}
    	}
    	if(f==-1) {
    		ll base=qpow(n,P-2);
    		rep(i,0,n-1) a[i]=a[i]*base%P;
    	}
    }
    
    void Solve(int l,int r) {  // CDQ计算dp
    	if(l==r) return;
    	int mid=(l+r)>>1;
    	Solve(l,mid);
    	int R=1,cc=-1;
    	while(R<=r-l+1) R<<=1,cc++;
    	rep(i,1,R) rev[i]=(rev[i>>1]>>1)|((i&1)<<cc);
    	rep(i,0,R) A[i]=B[i]=0;
    	rep(i,l,mid) A[i-l]=dp[i]*Inv[i]%P;
    	rep(i,1,r-l) B[i]=Inv[i-1]%P*Tr[i]%P;
    	NTT(R,A,1),NTT(R,B,1);
    	rep(i,0,R-1) A[i]=A[i]*B[i]%P;
    	NTT(R,A,-1);
    	rep(i,mid+1,r) dp[i]=(dp[i]+A[i-l]*Fac[i-1])%P;
    	Solve(mid+1,r);
    }
    
    int main(){
    	Fac[0]=Fac[1]=Inv[0]=Inv[1]=1;
    	rep(i,2,N-1) {
    		Fac[i]=Fac[i-1]*i%P;
    		Inv[i]=(P-P/i)*Inv[P%i]%P;
    	}
    	rep(i,2,N-1) Inv[i]=Inv[i-1]*Inv[i]%P;
    	Tr[1]=Tr[2]=1;
    	rep(i,3,N-1) Tr[i]=qpow(i,i-2); // n个点树的数量
    	dp[0]=1;
    	Solve(0,n);
    	int R=1,cc=-1;
    	while(R<=n*2) R<<=1,cc++;
    	rep(i,1,R) rev[i]=(rev[i>>1]>>1)|((i&1)<<cc);
    	rep(i,0,R) A[i]=B[i]=0;
    	rep(i,0,n) A[i]=dp[i]*Inv[i]%P;
    	rep(i,2,n) B[i]=Tr[i]*Inv[i-2]%P;
    	NTT(R,A,1),NTT(R,B,1);
    	rep(i,0,R-1) A[i]=A[i]*B[i]%P;
    	NTT(R,A,-1);
    	f[1]=1;
    	rep(i,2,n) f[i]=A[i]*Fac[i-2]%P; // 计算1,n联通的方案数
    	rep(kase,1,rd()) {
    		n=rd();
    		ll s1=1,s2=1;
    		rep(i,1,n) {
    			int x=rd();
    			s1=s1*dp[x]%P;
    			s2=s2*f[x]%P;
    		}
    		ll ans=(qpow(2,n))*s1-s2;
    		ans=(ans%P+P)%P;
    		printf("%lld\n",ans);
    	}
    }
    
    
  • 相关阅读:
    C#函数参数前的修饰符
    C#正则表达式简单案例解析
    C#正则表达式
    C#反射Reflection
    C#事件
    C#委托的使用
    无法解析主机报错
    SparkStreaming对接rabbitMQ
    load文件到hive,并保存
    kafka模式对比
  • 原文地址:https://www.cnblogs.com/chasedeath/p/12106476.html
Copyright © 2020-2023  润新知