• Noi2016十连测第二场黑暗 (二项式定理/斯特林数+CDQ+NTT)


    Noi2016十连测第二场-黑暗 (二项式定理/斯特林数+CDQ+NTT)

    题意: n 个点的无向图,每条边都可能存在,一个图的权值是连通块个数的 m 次方,求所有可能的图的权值和。

    考虑\(dp[i][j]\)表示\(j\)个点,权值为\(i\)次方

    我们首先要预处理出\(n\)个点无向联通图的数量\(g[i]\),模板题:BZOJ-3456 题解

    对于\(dp[i][j]\),枚举\(1\)号点所在的连通块大小为\(x\),那么可以得到的是\(dp[i][j]=\sum dp[k][j-x]\cdot C(j-1,x-1)\cdot C(i,k)\cdot g[x]\)(每次转移一个连通块,用二项式定理展开来求次方)

    利用\(CDQ+NTT\)可以完成转移,模板题:HDU-5730 题解

    #include<bits/stdc++.h>
    using namespace std;
    
    //#define int long long
    
    #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)
    #define Mod2(x) ((x<0)&&(x+=P))
    #define Mod1(x) ((x>=P)&&(x-=P))
    
    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 int N=1<<15|5,P=998244353;
    
    
    bool be;
    int n=3e4,m=15;
    int Inv[N],Fac[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;
    }
    
    int g[N],h[N],dp[16][N],w[N];
    int A[16][N],B[N];
    int rev[N];
    void NTT(int n,int *a,int f) {
    	rep(i,1,n-1) if(i<rev[i]) swap(a[i],a[rev[i]]);
    	for(reg int i=1;i<n;i<<=1) {
    		int len=n/i/2;
    		for(reg int l=0;l<n;l+=2*i) {
    			for(reg int j=l;j<l+i;j++) {
    				int t=1ll*a[j+i]*w[(j-l)*len]%P;
    				a[j+i]=a[j]-t; Mod2(a[j+i]);
    				a[j]=a[j]+t; Mod1(a[j]);
    			}
    		}
    	}
    	if(f==-1) {
    		ll base=qpow(n,P-2);
    		rep(i,0,n-1) a[i]=a[i]*base%P;
    	}
    }
    
    //预处理n个点联通图的数量g
    void Solve1(int l,int r) {
    	if(l==r) return;
    	int mid=(l+r)>>1;
    	Solve1(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);
    	w[0]=1,w[1]=qpow(3,(P-1)/R);
    	rep(i,2,R-1) w[i]=1ll*w[i-1]*w[1]%P;
    	rep(i,0,R) A[0][i]=B[i]=0;
    	rep(i,l,mid) A[0][i-l]=1ll*g[i]*Inv[i-1]%P;
    	rep(i,1,r-l) B[i]=1ll*Inv[i]*h[i]%P;
    	NTT(R,B,1),NTT(R,A[0],1);
    	rep(i,0,R-1) A[0][i]=1ll*A[0][i]*B[i]%P;
    	w[0]=1,w[1]=qpow((P+1)/3,(P-1)/R);
    	rep(i,2,R-1) w[i]=1ll*w[i-1]*w[1]%P;
    	NTT(R,A[0],-1);
    	rep(i,mid+1,r) g[i]=(g[i]-1ll*A[0][i-l]*Fac[i-1]%P+P)%P;
    	Solve1(mid+1,r);
    }
    
    int C[20][20];
    void Solve(int l,int r) {
    	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);
    	w[0]=1,w[1]=qpow(3,(P-1)/R);
    	rep(i,2,R-1) w[i]=1ll*w[i-1]*w[1]%P;
    	rep(i,0,R) B[i]=0;
    	rep(i,1,r-l) B[i]=1ll*Inv[i-1]*g[i]%P;
    	NTT(R,B,1);
    	rep(i,0,15) {
    		rep(j,0,R) A[i][j]=0;
    		rep(j,l,mid) A[i][j-l]=1ll*dp[i][j]*Inv[j]%P;
    		NTT(R,A[i],1);
    		rep(j,0,R-1) A[i][j]=1ll*A[i][j]*B[j]%P;
    	}
    	w[0]=1,w[1]=qpow((P+1)/3,(P-1)/R);
    	rep(i,2,R-1) w[i]=1ll*w[i-1]*w[1]%P;
    	rep(i,0,15) {
    		NTT(R,A[i],-1);
    		rep(j,mid+1,r) A[i][j-l]=1ll*A[i][j-l]*Fac[j-1]%P;
    	}
    	rep(i,mid+1,r) rep(j,0,15) rep(k,0,j) dp[j][i]=(dp[j][i]+1ll*C[j][k]*A[k][i-l])%P;
    	Solve(mid+1,r);
    }
    
    
    bool ed;
    int main(){
    	freopen("dark.in","r",stdin),freopen("dark.out","w",stdout);
    	Fac[0]=Fac[1]=Inv[0]=Inv[1]=1;
    	rep(i,2,n) {
    		Fac[i]=1ll*Fac[i-1]*i%P;
    		Inv[i]=1ll*(P-P/i)*Inv[P%i]%P;
    	}
    	rep(i,2,n) Inv[i]=1ll*Inv[i]*Inv[i-1]%P;
    	rep(i,0,0) dp[i][0]=1;
    	rep(i,0,n) g[i]=h[i]=qpow(2,i*(i-1)/2);
    	rep(i,0,m) {
    		C[i][0]=1;
    		rep(j,1,i)  C[i][j]=(C[i-1][j]+C[i-1][j-1])%P;
    	}
    	Solve1(1,n);
    	Solve(0,n);
    	rep(kase,1,rd()) {
    		n=rd(),m=rd();
    		printf("%d\n",dp[m][n]);
    	}
    }
    
  • 相关阅读:
    C++ 如何重复利用一个内存地址块
    C与C++在const用法上的区别
    C++ 与设计模式学习(其一)
    C/C++ 关于生成静态库(lib)/动态库(dll)文件如何使用(基于windows基础篇)
    c/c++----网站及其后门(CGI应用程序)
    C/C++深度copy和浅copy
    C/C++ 一段代码区分数组指针|指针数组|函数指针|函数指针数组
    C/C++ 如何劫持别人家的命令||函数||程序(只能对于window而言)
    C++继承与派生(原理归纳)
    Linux下如何查看自己的服务器有没有无线网卡
  • 原文地址:https://www.cnblogs.com/chasedeath/p/12106697.html
Copyright © 2020-2023  润新知