zoukankan      html  css  js  c++  java
  • 有标号的DAG计数I~IV

    有标号的DAG计数I

    题目链接

    COGS RIP

    个人题库

    分析

    感觉一下做不完,就四道题分开发博客(希望这不是个flag)。

    问题很简单,就是求$n$个结点的带标号DAG的个数,要求时间复杂度为$O(n^2)$。

    令$f[n]$为$n$个结点的带标号DAG的个数。

    一个思路是枚举入度为$0$的结点的个数,套上一篇博客的容斥计数的公式,可以得到:

    (f[n]=sum_{i=1}^{n}(-1)^{i-1} imes inom{n}{i} imes 2^{i(n-i)} imes f[n-i])

    直接递推即可。

    代码

    #include <bits/stdc++.h>
    #define rin(i,a,b) for(register int i=(a);i<=(b);++i)
    #define irin(i,a,b) for(register int i=(a);i>=(b);--i)
    #define trav(i,a) for(register int i=head[a];i;i=e[i].nxt)
    typedef long long LL;
    using std::cin;
    using std::cout;
    using std::endl;
    
    inline int read(){
    	int x=0,f=1;char ch=getchar();
    	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
    	while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
    	return x*f;
    }
    
    const int MAXN=5005;
    const LL MOD=10007;
    
    int n;
    LL fac[MAXN],invf[MAXN],f[MAXN];
    
    inline LL qpow(LL x,LL y){
    	LL ret=1,tt=x%MOD;
    	while(y){
    		if(y&1) ret=ret*tt%MOD;
    		tt=tt*tt%MOD;
    		y>>=1;
    	}
    	return ret;
    }
    
    inline LL binom(LL n,LL m){
    	if(n<0||m<0||n<m) return 0;
    	return fac[n]*invf[n-m]%MOD*invf[m]%MOD;
    }
    
    void init(){
    	fac[0]=1;
    	rin(i,1,n) fac[i]=fac[i-1]*i%MOD;
    	invf[n]=qpow(fac[n],MOD-2);
    	irin(i,n-1,0) invf[i]=invf[i+1]*(i+1)%MOD;
    }
    
    int main(){
    	n=read();
    	init();
    	f[0]=f[1]=1;
    	rin(i,2,n){
    		LL sgn=-1;
    		rin(j,1,i){
    			sgn=-sgn;
    			f[i]=(f[i]+sgn*binom(i,j)*qpow(2,j*(i-j))%MOD*f[i-j]%MOD+MOD)%MOD;
    		}
    	}
    	printf("%lld
    ",f[n]);
    	return 0;
    }
    

    有标号的DAG计数II

    题目链接

    个人题库

    分析

    好吧,几个小时前立的flag现在就拔了。

    考虑上一题的那个式子,看看能不能化成卷积的形式。

    好吧还真可以,

    (frac{f[n]}{n!sqrt{2}^{n^2}}=sum_{i=1}^{n}frac{(-1)^{i-1}}{i!sqrt{2}^{i^2}} imes frac{f[n-i]}{(n-i)!sqrt{2}^{(n-i)^2}})

    化的过程比较麻烦(并不)就不写在这里了。

    这里要用到二次剩余,提前算出来就好了。

    这就是一个$F(x)=F(x) imes G(x)+1$的形式,多项式求逆即可。

    时间复杂度为$O(n log n)$。

    代码

    #include <bits/stdc++.h>
    #define rin(i,a,b) for(register int i=(a);i<=(b);++i)
    #define irin(i,a,b) for(register int i=(a);i>=(b);--i)
    #define trav(i,a) for(register int i=head[a];i;i=e[i].nxt)
    typedef long long LL;
    using std::cin;
    using std::cout;
    using std::endl;
    
    inline int read(){
    	int x=0,f=1;char ch=getchar();
    	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
    	while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
    	return x*f;
    }
    
    const int MAXN=100005;
    const int NTT=1048576;
    const LL MOD=998244353;
    const LL G=3;
    const LL IG=332748118;
    const LL S=116195171;
    const LL IS=557219762;
    
    int N,n,m,len;
    LL w[NTT+5],iw[NTT+5];
    LL fac[MAXN],invf[MAXN];
    LL g[MAXN];
    LL rev[MAXN<<2],A[MAXN<<2],B[MAXN<<2];
    
    inline LL qpow(LL x,LL y){
    	LL ret=1,tt=x%MOD;
    	while(y){
    		if(y&1) ret=ret*tt%MOD;
    		tt=tt*tt%MOD;
    		y>>=1;
    	}
    	return ret;
    }
    
    void ntt(LL *c,int dft){
    	rin(i,0,n-1)
    		if(i<rev[i])
    			std::swap(c[i],c[rev[i]]);
    	for(register int mid=1;mid<n;mid<<=1){
    		int r=(mid<<1),u=NTT/r;
    		for(register int l=0;l<n;l+=r){
    			int v=0;
    			for(register int i=0;i<mid;++i,v+=u){
    				LL x=c[l+i],y=c[l+mid+i]*(dft>0?w[v]:iw[v])%MOD;
    				c[l+i]=x+y<MOD?x+y:x+y-MOD;
    				c[l+mid+i]=x-y>=0?x-y:x-y+MOD;
    			}
    		}
    	}
    	if(dft<0){
    		LL invn=qpow(n,MOD-2);
    		rin(i,0,n-1) c[i]=c[i]*invn%MOD;
    	}
    }
    
    void getinv(int mdx){
    	if(mdx==1){
    		A[0]=qpow(g[0],MOD-2);
    		return;
    	}
    	getinv((mdx+1)>>1);
    	m=(mdx-1)+((((mdx+1)>>1)-1)<<1),len=0;
    	for(n=1;n<=m;n<<=1) ++len;
    	rin(i,1,n-1) rev[i]=((rev[i>>1]>>1)|((i&1)<<(len-1)));
    	rin(i,0,n-1) B[i]=i<mdx?g[i]:0;
    	ntt(A,1);ntt(B,1);
    	rin(i,0,n-1) A[i]=(2*A[i]-B[i]*A[i]%MOD*A[i]%MOD+MOD)%MOD;
    	ntt(A,-1);
    	rin(i,mdx,n-1) A[i]=0;
    }
    
    void init(){
    	LL v=qpow(G,(MOD-1)/NTT),iv=qpow(IG,(MOD-1)/NTT);
    	w[0]=iw[0]=1;
    	rin(i,1,NTT-1) w[i]=w[i-1]*v%MOD,iw[i]=iw[i-1]*iv%MOD;
    	fac[0]=1;
    	rin(i,1,N) fac[i]=fac[i-1]*i%MOD;
    	invf[N]=qpow(fac[N],MOD-2);
    	irin(i,N-1,0) invf[i]=invf[i+1]*(i+1)%MOD;
    }
    
    int main(){
    	N=read();
    	init();
    	g[0]=0;
    	LL sgn=-1;
    	rin(i,1,N){
    		sgn=-sgn;
    		g[i]=(sgn*invf[i]*qpow(IS,1ll*i*i)%MOD+MOD)%MOD;
    	}
    	g[0]=1;
    	rin(i,1,N){
    		g[i]=(MOD-g[i])%MOD;
    	}
    	getinv(N+1);
    	printf("%lld
    ",A[N]*fac[N]%MOD*qpow(S,1ll*N*N)%MOD);
    	return 0;
    }
    

    有标号的DAG计数III

    题目链接

    个人题库

    分析

    要求DAG弱连通。

    我们先需要递推出DAGCNT1的$f$数组,然后从所有的DAG中减去不满足弱连通的。

    类比城市规划那道题的思路,考虑枚举$1$号结点所在弱连通块的大小,可以得到状态转移方程:

    (g[n]=f[n]-sum_{i=1}^{n-1}inom{n-1}{i-1} imes g[i] imes f[n-i])

    其中$g[n]$表示$n$个结点的带标号弱连通DAG的个数。

    时间复杂度为$O(n^2)$。

    代码

    #include <bits/stdc++.h>
    #define rin(i,a,b) for(register int i=(a);i<=(b);++i)
    #define irin(i,a,b) for(register int i=(a);i>=(b);--i)
    #define trav(i,a) for(register int i=head[a];i;i=e[i].nxt)
    typedef long long LL;
    using std::cin;
    using std::cout;
    using std::endl;
    
    inline int read(){
    	int x=0,f=1;char ch=getchar();
    	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
    	while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
    	return x*f;
    }
    
    const int MAXN=5005;
    const LL MOD=998244353;
    
    int n;
    LL fac[MAXN],invf[MAXN],pow2[MAXN*MAXN/4],f[MAXN],g[MAXN];
    
    inline LL qpow(LL x,LL y){
    	LL ret=1,tt=x%MOD;
    	while(y){
    		if(y&1) ret=ret*tt%MOD;
    		tt=tt*tt%MOD;
    		y>>=1;
    	}
    	return ret;
    }
    
    inline LL binom(LL n,LL m){
    	if(n<0||m<0||n<m) return 0;
    	return fac[n]*invf[n-m]%MOD*invf[m]%MOD;
    }
    
    void init(){
    	fac[0]=1;
    	rin(i,1,n) fac[i]=fac[i-1]*i%MOD;
    	invf[n]=qpow(fac[n],MOD-2);
    	irin(i,n-1,0) invf[i]=invf[i+1]*(i+1)%MOD;
    	int lim=(n/2)*(n/2+1);pow2[0]=1;
    	rin(i,1,lim) pow2[i]=(pow2[i-1]<<1)%MOD;
    }
    
    int main(){
    	n=read();
    	init();
    	f[0]=f[1]=1;
    	rin(i,2,n){
    		LL sgn=-1;
    		rin(j,1,i){
    			sgn=-sgn;
    			f[i]=(f[i]+sgn*binom(i,j)*pow2[j*(i-j)]%MOD*f[i-j]%MOD+MOD)%MOD;
    		}
    	}
    	g[0]=g[1]=1;
    	rin(i,2,n){
    		rin(j,1,i-1){
    			g[i]=(g[i]+binom(i-1,j-1)*g[j]%MOD*f[i-j])%MOD;
    		}
    		g[i]=(f[i]-g[i]+MOD)%MOD;
    	}
    	printf("%lld
    ",g[n]);
    	return 0;
    }
    

    有标号的DAG计数IV

    题目链接

    个人题库

    分析

    上一个式子可以化成这样:

    (frac{g[n]}{(n-1)!}=frac{f[n]}{(n-1)!}-sum_{i=1}^{n-1}frac{g[i]}{(i-1)!} imes frac{f[n-i]}{(n-i)!})

    也就是这样的形式:

    (G(x)=F(x)-G(x) imes H(x))

    (G(x)=frac{F(x)}{H(x)+1})

    多项式求逆就好了,时间复杂度为$O(n log n)$。

    话说多项式的常数项是真玄学。

    还有一个非常神仙的多项式求$ln$的做法,可以看这篇博客

    代码

    #include <bits/stdc++.h>
    #define rin(i,a,b) for(register int i=(a);i<=(b);++i)
    #define irin(i,a,b) for(register int i=(a);i>=(b);--i)
    #define trav(i,a) for(register int i=head[a];i;i=e[i].nxt)
    typedef long long LL;
    using std::cin;
    using std::cout;
    using std::endl;
    
    inline int read(){
    	int x=0,f=1;char ch=getchar();
    	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
    	while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
    	return x*f;
    }
    
    const int MAXN=100005;
    const int NTT=1048576;
    const LL MOD=998244353;
    const LL G=3;
    const LL IG=332748118;
    const LL S=116195171;
    const LL IS=557219762;
    
    int N,n,m,len;
    LL w[NTT+5],iw[NTT+5];
    LL f[MAXN],g[MAXN],h[MAXN];
    LL fac[MAXN],invf[MAXN];
    int rev[MAXN<<2];
    LL A[MAXN<<2],B[MAXN<<2];
    
    inline LL qpow(LL x,LL y){
    	LL ret=1,tt=x%MOD;
    	while(y){
    		if(y&1) ret=ret*tt%MOD;
    		tt=tt*tt%MOD;
    		y>>=1;
    	}
    	return ret;
    }
    
    void ntt(LL *c,int dft){
    	rin(i,0,n-1)
    		if(i<rev[i])
    			std::swap(c[i],c[rev[i]]);
    	for(register int mid=1;mid<n;mid<<=1){
    		int r=(mid<<1),u=NTT/r;
    		for(register int l=0;l<n;l+=r){
    			int v=0;
    			for(register int i=0;i<mid;++i,v+=u){
    				LL x=c[l+i],y=c[l+mid+i]*(dft>0?w[v]:iw[v])%MOD;
    				c[l+i]=(x+y<MOD?x+y:x+y-MOD);
    				c[l+mid+i]=(x-y>=0?x-y:x-y+MOD);
    			}
    		}
    	}
    	if(dft<0){
    		LL invn=qpow(n,MOD-2);
    		rin(i,0,n-1) c[i]=c[i]*invn%MOD;
    	}
    }
    
    void getinv(LL *c,int mdx){
    	if(mdx==1){
    		A[0]=qpow(c[0],MOD-2);
    		return;
    	}
    	getinv(c,(mdx+1)>>1);
    	m=(mdx-1)+((((mdx+1)>>1)-1)<<1);
    	for(n=1,len=0;n<=m;n<<=1,++len);
    	rin(i,1,n-1) rev[i]=((rev[i>>1]>>1)|((i&1)<<(len-1)));
    	rin(i,0,n-1) B[i]=i<mdx?c[i]:0;
    	ntt(A,1);ntt(B,1);
    	rin(i,0,n-1) A[i]=(2*A[i]-B[i]*A[i]%MOD*A[i]%MOD+MOD)%MOD;
    	ntt(A,-1);
    	rin(i,mdx,n-1) A[i]=0;
    }
    
    void init(){
    	fac[0]=1;
    	rin(i,1,N) fac[i]=fac[i-1]*i%MOD;
    	invf[N]=qpow(fac[N],MOD-2);
    	irin(i,N-1,0) invf[i]=invf[i+1]*(i+1)%MOD;
    	LL v=qpow(G,(MOD-1)/NTT),iv=qpow(IG,(MOD-1)/NTT);w[0]=iw[0]=1;
    	rin(i,1,NTT-1) w[i]=w[i-1]*v%MOD,iw[i]=iw[i-1]*iv%MOD;
    }
    
    int main(){
    	N=read();
    	init();
    	g[0]=0;
    	LL sgn=-1;
    	rin(i,1,N){
    		sgn=-sgn;
    		g[i]=(sgn*invf[i]*qpow(IS,1ll*i*i)%MOD+MOD)%MOD;
    	}
    	g[0]=1;
    	rin(i,1,N) g[i]=(MOD-g[i])%MOD;
    	getinv(g,N+1);
    	rin(i,1,N) A[i]=A[i]*fac[i]%MOD*qpow(S,1ll*i*i)%MOD,f[i]=A[i]*invf[i-1]%MOD,h[i]=A[i]*invf[i]%MOD;
    	f[0]=0;
    	memset(A,0,sizeof A);
    	h[0]=1;
    	getinv(h,N+1);
    	m=(N<<1);
    	for(n=1,len=0;n<=m;n<<=1,++len);
    	rin(i,1,n-1) rev[i]=((rev[i>>1]>>1)|((i&1)<<(len-1)));
    	rin(i,0,n-1) B[i]=i<=N?f[i]:0;
    	ntt(A,1);ntt(B,1);
    	rin(i,0,n-1) A[i]=A[i]*B[i]%MOD;
    	ntt(A,-1);
    	printf("%lld
    ",A[N]*fac[N-1]%MOD);
    	return 0;
    }
    
  • 相关阅读:
    员工看公司
    Java多线程
    Intellij热部署插件JRebel的详细配置及图解
    Java并发处理锁 Lock
    Java8 Stream
    Eclipse下Maven安装和配置
    IntelliJ IDEA 部署Tomcat及创建一个web工程
    IntelliJ IDEA 下搭建vue项目工程
    Vue Router路由管理器介绍
    用WebStorm搭建vue项目
  • 原文地址:https://www.cnblogs.com/ErkkiErkko/p/10396744.html
Copyright © 2011-2022 走看看