zoukankan      html  css  js  c++  java
  • [LOJ2983] [WC2019] 数树

    题目链接

    LOJ:https://loj.ac/problem/2983

    BZOJ:https://lydsy.com/JudgeOnline/problem.php?id=5475

    洛谷:https://www.luogu.org/problemnew/show/P5206

    Soltion

    超级毒瘤数数题...窝看了一晚上才看懂...

    %%%rqy


    subtask 0

    很容易可以看出每个公共的边连成的联通块被绑一起了,所以答案就是(y^{p}),其中(p)为联通块个数。

    也就是说答案为(y^{n-m}),其中(m)为公共边条数。


    Subtask 1

    (f(s)=y^{n-|s|}),其中(s)为边集。

    那么我们枚举树的形态可以得到答案为:

    [ans=sum_{E_2}f(E_1cap E_2) ]

    其中(E_2)枚举的是边集,(E_1)是题目给出的边集。这个(E_1cap E_2)不是很好办,我们可以利用下面这个容斥的式子化简:

    [f(s)=sum_{tsubseteq s}sum_{p subseteq t}(-1)^{|t|-|p|}f(p) ]

    下面会给出证明,当然,读者自证不难。带进去可得:

    [egin{align} ans&=sum_{E_2}sum_{tsubseteq E_1,tsubseteq E_2}sum_{psubseteq t}(-1)^{|t|-|p|}f(p)\ &=sum_{tsubseteq E_1} g(t)sum_{psubseteq t}(-1)^{|t|-|p|}f(p)\ &=sum_{tsubseteq E_1} g(t)sum_{psubseteq t}(-1)^{|t|-|p|}y^{n-|p|}\ &=sum_{tsubseteq E_1} g(t)y^{n-|t|}sum_{psubseteq t}(-1)^{|t|-|p|}y^{|t|-|p|}\ &=sum_{tsubseteq E_1} g(t)y^{n-|t|}sum_{psubseteq t}(-y)^{|t|-|p|}\ &=sum_{tsubseteq E_1} g(t)y^{n-|t|}(1-y)^{|t|}\ end{align} ]

    其中倒数第二个等式到最后一个等式用到了二项式定理,(g(s))表示包含边集(s)的树的个数,可以证明:

    [g(s)=n^{k-2}prod_{i=1}^ka_i ]

    证明下面会给出,其中,(s)被分成了(k)个联通块,大小分别为(a_1,a_2cdots a_k)

    这套交换求和符号还是看得懂的吧...不然怎么敢来刚这个题

    那么带进去可得:

    [egin{align} ans&=sum_{tsubseteq E_1}left(n^{k-2}prod_{i=1}^ka_i ight)y^{k}(1-y)^{n-k}\ &=frac{(1-y)^n}{n^2}sum_{tsubseteq E_1}prod_{i=1}^{k}frac{ny}{1-y}cdot a_i end{align} ]

    至此,这玩意还是指数级的,但是我们可以发现,(frac{ny}{1-y})是固定的,设其为(k),也就是说,每个联通块有(ka_i)的贡献。

    那么,其实我们就可以(dp)了,设(f_{i,j})表示第(i)个点,只考虑子树(i)所在的联通块(size)(j)的贡献,注意不考虑(i)这个联通块的贡献(这里贡献指的是若干个(ka_i)的乘积)。

    然后直接暴力背包转移,时间复杂度降为了(O(n^2))

    我们固定(f_{i,j})(i),设一个幂级数:

    [F_i(x)=sum_{j=1}^nf_{i,j}x^j ]

    再设答案为(g_i(x))

    [g_i(x)=sum_{j=1}^{n}kjcdot f_{i,j}=kF_i'(1) ]

    其中(F_i')表示求导。

    那么背包转移就可以写成:

    [F_i(x)=xprod_{jin son_i}(g_j+F_j(x)) ]

    那么可得:

    [F_i'(x)=prod_{jin son_i}(g_j+F_j(x))+xleft(prod_{jin son_i}(g_j+F_j(x)) ight)left(sum_{jin son_i}frac{F_j'(x)}{g_j+F_j(x)} ight) ]

    这里是照抄的乘积求导法则:

    [left(prod_{i=1}^kF_i(x) ight)'=sum_{i=1}^{n}F_i'(x)prod_{j e i}^kF_j(x) ]

    换一下求和符号就是:

    [left(prod_{i=1}^kF_i(x) ight)'=prod_{i=1}^{k}F_i(x)sum_{j=1}^{k}frac{F_j'(x)}{F_j(x)} ]

    然后令(x=1)得到:

    [F_i'(1)=prod_{jin son_i}(g_j+F_j(1))+left(prod_{jin son_i}(g_j+F_j(1)) ight)left(sum_{jin son_i}frac{F_j'(1)}{g_j+F_j(1)} ight) ]

    (t_i=F_i(1)=prod_{jin son_i}(g_j+F_j(x))),那么把上面的式子抄下来:

    [g_i=F_i'(1)=t_i(1+sum_{jin son_i}frac{g_j}{g_j+t_j}) ]

    [t_i=prod_{jin son}g_j+t_j ]

    然后(O(n))递推就好了。


    Subtask 2

    我们照抄上面的式子:

    [ans=sum_{s}y^{n-|s|}(1-y)^{|s|}g^2(s) ]

    这里(s)枚举的是公共部分,所以(g(s))要平方,因为两棵树都要满足。

    展开:

    [ans=frac{(1-y)^n}{n^4}sum_{s}prod_{i=1}^{k}left(frac{n^2y}{1-y} ight)a_i^2 ]

    过程和上面一样。

    我们换种方式枚举,枚举多少个联通块以及大小分别是多少,显然联通块无顺序所以要除以(k!),然后点有标号所以乘上组合数(frac{n!}{prod a_i!})(a)个点的树个数为(a^{a-2}),写出来就是:

    [egin{align} ans&=frac{(1-y)^n}{n^4}sum_{k=1}^nfrac{1}{k!}sum_{a_1+a_2cdots a_k=n}frac{n!}{prod a_i!}prod_{i=1}^{k}left(frac{n^2y}{1-y} ight)a_i^2cdot a_i^{a_i-2}\ &=frac{(1-y)^nn!}{n^4}sum_{k=1}^nfrac{1}{k!}sum_{a_1+a_2cdots a_k=n}prod_{i=1}^{k}left(frac{n^2y}{1-y} ight)frac{a_i^{a_i}}{a_i!}\ end{align} ]

    后面的卷积形式写成多项式就是:

    [egin{align} ans&=frac{(1-y)^nn!}{n^4}[x^n]sum_{k=1}^nfrac{1}{k!}left(sum_{a=1}^{+infty}frac{n^2y}{1-y}cdot frac{a^a}{a!}x^a ight)^k\ end{align} ]

    注意到这是个多项式(exp)形式,直接算就好了,复杂度(O(nlog n))

    其实多项式(exp)的组合意义就是带标号的多重背包,所以上面符合多项式(exp)的式子也就不出意外了。


    上面lemma的proof

    lemma 1

    [f(s)=sum_{tsubseteq s}sum_{p subseteq t}(-1)^{|t|-|p|}f(p) ]

    其中(f)是一个和(|s|)有关的函数。

    那么交换求和符号:

    [f(s)=sum_{p subseteq s}f(p)sum_{psubseteq t,tsubseteq s}(-1)^{|t|-|p|} ]

    [f(s)=sum_{p subseteq s}f(p)sum_{tsubseteq s-p}(-1)^{|t|} ]

    [f(s)=sum_{p subseteq s}f(p)[s=p] ]

    可以发现等式左边右边都是(f(s)),证毕。


    lemma 2:

    我们要把(k)个联通块组成的森林连成一棵树的方案数,其中联通块大小分别为(a_1,a_2cdots a_k)

    考虑每个联通块视为一个点,那么它的(prufer)序列共有(k-2)个点,每个点在([1,k])

    我们枚举每个点是什么,统计方案数:

    [sum_{1leqslant b_1,b_2cdots b_{k-2}leqslant k}prod_{i=1}^{k}a_i^{c_i+1} ]

    其中(b_i)为第(i)位的数,(c_i)表示(i)出现了多少次。

    交换求和符号:

    [egin{align} &left(prod_{i=1}^ka_i ight)sum_{1leqslant b_1,b_2cdots b_{k-2}leqslant k}prod_{i=1}^{k}a_i^{c_i}\ =&left(prod_{i=1}^ka_i ight)prod_{i=1}^{k-2}sum_{x=1}^{k}a_{x}\ =&left(prod_{i=1}^ka_i ight)prod_{i=1}^{k-2}n\ =&n^{n-2}prod_{i=1}^ka_i\ end{align} ]


    注意当(y=1)时上面很多式子都没有意义,需要特判。

    #include<bits/stdc++.h>
    using namespace std;
    
    void read(int &x) {
        x=0;int f=1;char ch=getchar();
        for(;!isdigit(ch);ch=getchar()) if(ch=='-') f=-f;
        for(;isdigit(ch);ch=getchar()) x=x*10+ch-'0';x*=f;
    }
    
    void print(int x) {
        if(x<0) putchar('-'),x=-x;
        if(!x) return ;print(x/10),putchar(x%10+48);
    }
    void write(int x) {if(!x) putchar('0');else print(x);putchar('
    ');}
    
    #define lf double
    #define ll long long 
    #define mp make_pair
    #define fr first
    #define sc second
    
    const int maxn = 6e5+10;
    const int inf = 1e9;
    const lf eps = 1e-8;
    const int mod = 998244353;
    
    int add(int x,int y) {return x+y>mod?x+y-mod:x+y;}
    int del(int x,int y) {return x-y<0?x-y+mod:x-y;}
    int mul(int x,int y) {return 1ll*x*y-1ll*x*y/mod*mod;}
    
    int n,y;
    
    int qpow(int a,int x) {
    	int res=1;
    	for(;x;x>>=1,a=mul(a,a)) if(x&1) res=mul(res,a);
    	return res;
    }
    
    namespace fuckpps {
    	void solve(int op) {
    		if(op==0) write(1);
    		else if(op==1) write(qpow(n,n-2));
    		else write(qpow(n,2*n-4));
    	}
    }
    
    namespace subtask0 {
    	void solve() {
    		map<pair<int,int >,int > s;int ans=0;
    		for(int i=1,x,yy;i<n;i++) read(x),read(yy),s[mp(min(x,yy),max(x,yy))]=1;
    		for(int i=1,x,yy;i<n;i++) read(x),read(yy),ans+=s[mp(min(x,yy),max(x,yy))];
    		write(qpow(y,n-ans));
    	}
    }
    
    namespace subtask1 {
    	int head[maxn],tot,g[maxn],t[maxn],k;
    	struct edge{int to,nxt;}e[maxn<<1];
    
    	void ad(int u,int v) {e[++tot]=(edge){v,head[u]},head[u]=tot;}
    	void ins(int u,int v) {ad(u,v),ad(v,u);}
    	
    	void dfs(int x,int fa) {
    		g[x]=k,t[x]=1;
    		for(int i=head[x],v;i;i=e[i].nxt)
    			if((v=e[i].to)!=fa) {
    				dfs(v,x);
    				g[x]=add(g[x],mul(g[v],qpow(add(g[v],t[v]),mod-2)));
    				t[x]=mul(t[x],add(g[v],t[v]));
    			}
    		g[x]=mul(g[x],t[x]);
    	}
    	
    	void solve() {
    		k=mul(mul(n,y),qpow(1-y+mod,mod-2));
    		for(int i=1,u,v;i<n;i++) read(u),read(v),ins(u,v);
    		dfs(1,0);write(mul(g[1],mul(qpow(1-y+mod,n),qpow(mul(n,n),mod-2))));
    	}
    }
    
    namespace subtask2 {
    	int w[maxn],rw[maxn],N,bit,pos[maxn],f[maxn],fac[maxn],ifac[maxn],inv[maxn],mxn,g[maxn],K;
    	int tmp[7][maxn];
    	
    	void ntt_init() {
    		w[0]=rw[0]=1,w[1]=qpow(3,(mod-1)/N);mxn=N;
    		for(int i=2;i<=N;i++) w[i]=mul(w[i-1],w[1]);
    		rw[1]=qpow(w[1],mod-2);
    		for(int i=2;i<=N;i++) rw[i]=mul(rw[i-1],rw[1]);
    	}
    	
    	void ntt(int *r,int op) {
    		for(int i=1;i<N;i++) if(pos[i]>i) swap(r[i],r[pos[i]]);
    		for(int i=1,d=mxn>>1;i<N;i<<=1,d>>=1)
    			for(int j=0;j<N;j+=i<<1)
    				for(int k=0;k<i;k++) {
    					int x=r[j+k],y=mul((op==1?w:rw)[k*d],r[i+j+k]);
    					r[j+k]=add(x,y),r[i+j+k]=del(x,y);
    				}
    		if(op==-1) {int d=qpow(N,mod-2);for(int i=0;i<N;i++) r[i]=mul(r[i],d);}
    	}
    	
    	void ntt_get(int len) {
    		for(N=1,bit=0;N<=len;N<<=1,bit++) ;
    		for(int i=0;i<N;i++) pos[i]=pos[i>>1]>>1|((i&1)<<(bit-1));
    	}
    	
    	void poly_inv(int *r,int *t,int len) {
    		if(len==1) return t[0]=qpow(r[0],mod-2),void();
    		poly_inv(r,t,len>>1);
    		for(int i=0;i<len>>1;i++) tmp[0][i]=t[i],tmp[1][i]=r[i];
    		for(int i=len>>1;i<len;i++) tmp[0][i]=0,tmp[1][i]=r[i];
    		ntt_get(len),ntt(tmp[0],1),ntt(tmp[1],1);
    		for(int i=0;i<N;i++) t[i]=del(mul(2,tmp[0][i]),mul(mul(tmp[1][i],tmp[0][i]),tmp[0][i]));
    		ntt(t,-1);for(int i=len;i<N;i++) t[i]=0;
    		for(int i=0;i<len<<1;i++) tmp[0][i]=tmp[1][i]=0;
    	}
    	
    	void poly_der(int *r,int *t,int len) {
    		ntt_get(len);
    		for(int i=1;i<len;i++) t[i-1]=mul(i,r[i]);
    		for(int i=len-1;i<N;i++) t[i]=0;
    	}
    	
    	void poly_int(int *r,int *t,int len) {
    		ntt_get(len);
    		for(int i=0;i<len;i++) t[i+1]=mul(inv[i+1],r[i]);t[0]=0;
    		for(int i=len+1;i<N;i++) t[i]=0;
    	}
    	
    	void poly_ln(int *r,int *t,int len) {
    		poly_der(r,tmp[2],len);
    		poly_inv(r,tmp[3],len);
    		ntt_get(len),ntt(tmp[2],1),ntt(tmp[3],1);
    		for(int i=0;i<N;i++) tmp[3][i]=mul(tmp[2][i],tmp[3][i]);
    		ntt(tmp[3],-1);
    		poly_int(tmp[3],t,len);
    		for(int i=0;i<len<<1;i++) tmp[3][i]=0;
    	}
    	
    	void poly_exp(int *r,int *t,int len) {
    		if(len==1) return t[0]=1,void();
    		poly_exp(r,t,len>>1);
    		for(int i=0;i<len>>1;i++) tmp[4][i]=r[i],tmp[5][i]=t[i];
    		for(int i=len>>1;i<len;i++) tmp[4][i]=r[i],tmp[5][i]=0;
    		poly_ln(tmp[5],tmp[6],len);
    		for(int i=0;i<len;i++) tmp[4][i]=del(tmp[4][i],tmp[6][i]);
    		tmp[4][0]=add(tmp[4][0],1);
    		ntt_get(len),ntt(tmp[4],1),ntt(tmp[5],1);
    		for(int i=0;i<N;i++) t[i]=mul(tmp[4][i],tmp[5][i]);
    		ntt(t,-1);for(int i=len;i<N;i++) t[i]=0;
    	}
    	
    	void solve() {
    		for(N=1,bit=0;N<=n<<2;N<<=1,bit++);ntt_init();
    		inv[0]=fac[0]=ifac[0]=inv[1]=1;K=mul(mul(n,mul(n,y)),qpow(1-y+mod,mod-2));
    		for(int i=2;i<N;i++) inv[i]=mul(mod-mod/i,inv[mod%i]);
    		for(int i=1;i<N;i++) fac[i]=mul(fac[i-1],i);
    		for(int i=1;i<N;i++) ifac[i]=mul(ifac[i-1],inv[i]);
    		for(int i=1;i<=n;i++) f[i]=mul(K,mul(qpow(i,i),ifac[i]));
    		ntt_get(n);poly_exp(f,g,N);
    		write(mul(g[n],mul(mul(qpow(1-y+mod,n),fac[n]),qpow(qpow(n,4),mod-2))));
    	}
    }
    
    int main() {
    	read(n),read(y);int op;read(op);
    	if(y==1) fuckpps :: solve(op);
    	else if(op==0) subtask0 :: solve();
    	else if(op==1) subtask1 :: solve();
    	else subtask2 :: solve();
    	return 0;
    }
    
  • 相关阅读:
    通过Get-Group导出组的成员
    VNC Server (CentOS 7 GNOME)
    VNC Server (Ubuntu 16.04.3 GNOME)
    输入输出重定向
    Linux下的网卡Bonding
    硬件性能测试
    Linux里的稀疏文件
    Linux下CPU信息的查看
    工作中常用到的Linux命令
    Putty+Xming实现在Windows客户端显示Linux服务器端的图形化程序
  • 原文地址:https://www.cnblogs.com/hbyer/p/10677674.html
Copyright © 2011-2022 走看看