zoukankan      html  css  js  c++  java
  • BZOJ 5475: [WC 2019] 数树

    WC2019 数树

    • 解决了一个心头大患
    • 考试的时候本人太智障了QwQ
    • 本文的参考链接,膜了一发rqy的题解

    题目链接

    Subtask 0

    好像可以直接做...

    推一推就能发现,是$y^k$,其中$k$表示相同的边构成的联通块数...

    (我在考试的时候,丝毫都没有意识到这是$n-边数$

    namespace Subtask1
    {
    	int main()
    	{
    		int cnt=n;
    		for(int x=1;x<=n;x++)
    		{
    			for(int i=head[x];i!=-1;i=e[i].next)
    			{
    				int to1=e[i].to;
    				if(to1<x&&mp[x].find(to1)!=mp[x].end())cnt--;
    			}
    		}
    		printf("%d
    ",q_pow(y,cnt));
    		return 0;
    	}
    }
    

    Subtask 1

    这个就很麻烦了qwq

    我们知道,$ans=y^{n-|E_1 cap E_2|}$

    通过简单容斥原理,我们可以得到:$f(S)=sumlimits_{Tsubseteq S}sumlimits_{Psubseteq T}(-1)^{|T|-|P|} imes f(P)$

    对于这样的问题,有一个简易的想法,就是枚举相同边集,也就是$f(S)=y^{n-|S|},S=E_1cap E_2$

    那么答案就是:$sumlimits_{E_2}y^{n-|E_1cap E_2|}=sumlimits_{E_2}sumlimits_{Ssubseteq|E_1 cap E_2|}sumlimits_{Psubseteq S}(-1)^{|S|-|P|} imes y^{n-|P|}$

    那么考虑变换枚举顺序:$sumlimits_{Ssubseteq E_1}(sumlimits_{Psubseteq S}(-1)^{|S|-|P|} imes y^{n-|P|})sumlimits_{E_2}[Ssubseteq E_2]$

    对于最后一项,表达的是包含$S$边集的生成树的个数,根据Prufer序列凯莱公式可以得到:$sumlimits_{Ssubseteq E_1}(n^{n-|S|-2} imes prodlimits_{i=1}^{n-|S|} a_i) sumlimits_{Psubseteq S}(-1)^{|S|-|P|} imes y^{n-|P|}$,其中$a_i$表示每个联通块的大小

    为了方便表示,我们设:$g(S)=n{n-|S|-2} imesprodlimits_{i=1}K a_i $

    然后考虑如何去掉$P$的枚举,对于$(1-y)k=sumlimits_{i=0}k C(k,i) imes (-1)^i imes y^i=sumlimits_{Ssubseteq T}(-y)^{|S|}$,其中$|T|=k$

    所以我们考虑将$y^{n-|P|}$转化一下得到:$sumlimits_{Ssubseteq E_1}g(S) imes y^{n-|S|}sumlimits_{Psubseteq S}(-y)^{|S|-|P|}$

    那么对于上面的式子可以变成:$sumlimits_{Ssubseteq E_1} g(S) imes y^{n-|S|} imes (1-y)^{|S|}$

    然后展开$g(S)$:$sumlimits_{Ssubseteq E_1} n^{n-|S|-2} imes y^{n-|S|} imes (1-y)^{|S|} prodlimits_{i=1}^{n-|S|} a_i $

    设:$n-|S|=k$

    那么:$sumlimits_{Ssubseteq E_1}n^{k-2} imes y^k imes (1-y)^{n-k} imes prodlimits_{i=1}^ka_i$

    然后把$(1-y){n-k},n{k-2}$转化一下,提出去可以得到:$frac{(1-y)n}{n2}sumlimits_{Ssubseteq E_1} (frac{n imes y}{1-y})^k prodlimits_{i=1}^k a_i$

    然后设:$K=frac{n imes y}{1-y}$,忽略掉前面的常数:$sumlimits_{Ssubseteq E_1}K^k prodlimits_{i=1}^k a_i=sumlimits_{Ssubseteq E_1}prodlimits_{i=1}^k a_i imes K$

    那么相当于是对于每个联通块有大小乘以$K$的贡献,这个东西显然可以树形背包...

    但是这样是$n^2$的,非常讲道理...

    那我们考虑背包的多项式有没有什么神奇的性质...

    对于答案,我们设:$g(x)=Ksumlimits_{i=1} i imes f_x(i)$

    设:$F_p(x)=sumlimits_{i=1}x^i imes f_p(i)$

    那么显然$g(x)=K imes F_x'(1)$

    对于$F_u(x)$的转移很显然,$F_u(x)=xprodlimits_{v}(F_v(x)+g(v))$

    那么根据导数的定义:$g(u)=K imes prodlimits_{v}(F_v(1)+g(v))+(prodlimits_{v} (F_v(1)+g(v)))sumlimits_{v}frac{K imes F_v'(1)}{g(v)+F_v(1)}$

    那么我们发现,如果需要维护$g(x)$那么只需要维护$F_x(1)$即可。

    所以设:$f(x)=F_x(1)$,因此:$g(u)=f(u) imes (K+sumlimits_{v} frac{g(v)}{g(v)+f(v)})$

    那么就完事了qwq

    namespace Subtask2
    {
    	int f[N],g[N],K;
    	void dfs(int x,int from)
    	{
    		g[x]=K,f[x]=1;
    		for(int i=head[x];i!=-1;i=e[i].next)
    		{
    			int to1=e[i].to;
    			if(to1!=from)
    			{
    				dfs(to1,x);
    				g[x]=(g[x]+(ll)g[to1]*inv(g[to1]+f[to1]))%mod;
    				f[x]=(ll)f[x]*(g[to1]+f[to1])%mod;
    			}
    		}
    		g[x]=(ll)g[x]*f[x]%mod;
    	}
    	int main()
    	{
    		if(y==1)return printf("%d
    ",q_pow(n,n-2)),0;
    		K=(ll)n*y%mod*inv(1-y)%mod;dfs(1,0);
    		printf("%lld
    ",((ll)g[1]*q_pow(n,mod-3)%mod*q_pow(1-y,n)%mod+mod)%mod);
    		return 0;
    	}
    }
    

    Subtask 2

    根据上面的容斥原理,我们想到一个绝妙的方法,那就是俩都枚举一下试试

    $sumlimits_{S}g(S)^2 imes y^{n-|S|} imes (1-y)^{|S|}$

    然后同样展开qwq

    $frac{(1-y)n}{n4}sumlimits_S prodlimits_{i=1}^k a_i^2 frac{n^2 imes y}{1-y}$

    然后同样设:$K=frac{n^2 imes y}{1-y}$

    同样发现,可以得到:$frac{(1-y)n}{n4}sumlimits_{S}prodlimits_{i=1}^k a_i^2 imes K$

    然后.........麻麻我不会了..........

    那么证明,我们不能通过枚举边集来解决...

    所以,我们考虑所有的联通块具有什么样的性质,显然,他们所有联通块的点数之和恰好为$n$,好吧,这是一句废话qwq

    对于这样的形式,我们可以考虑枚举每个联通块也就是说,我们可以得到:$frac{(1-y)n}{n4}sumlimits_{(sumlimits_{i=1}^k a_i)=n}frac{1}{k!}prodlimits_{i=1}^k frac{a_i^{a_i-2}}{a_i!} imes a_i^2 imes K$

    那么稍微化简化简:$frac{(1-y)n}{n4}sumlimits_{(sumlimits_{i=1}^k a_i)=n}frac{1}{k!}prodlimits_{i=1}^k K imesfrac{a_i^{a_i}}{a_i!}$

    我们发现:多项式出现了!

    $ frac{(1-y)n}{n4}[ x^n ] (sumlimits_{k=1} frac{1}{k!} prodlimits_{i=1}^k f(x)) $,其中,$ f(x)=sumlimits_{i=1} frac{i^i}{i!} x^i $

    然后可以发现,前面的式子就是:$frac{(1-y)n}{n4} [ x^n ] ( sumlimits_{k=1} frac{1}{k!} f(x)^k )$

    然后,对于$e^x$麦克劳林展开:$sumlimits_{i=0} frac{x^k}{i!}$

    那么对于后面的那个式子,本质就是$e^{f(x)}$

    那么直接多项式exp就好了qwq

    namespace Subtask3
    {
    	int A[N<<2],B[N<<2];
    	void NTT(int *a,int len,int flag)
    	{
    		int i,j,k,t,w,x,tmp;
    		for(i=k=0;i<len;i++)
    		{
    			if(i>k)swap(a[i],a[k]);
    			for(j=len>>1;(k^=j)<j;j>>=1);
    		}
    		for(k=2;k<=len;k<<=1)
    		{
    			t=k>>1;x=q_pow(3,(mod-1)/k);if(flag==-1)x=inv(x);
    			for(i=0;i<len;i+=k)
    				for(w=1,j=i;j<i+t;j++,w=(ll)w*x%mod)
    					tmp=(ll)w*a[j+t]%mod,a[j+t]=(a[j]-tmp)%mod,a[j]=(a[j]+tmp)%mod;
    		}if(flag==-1)for(t=inv(len),i=0;i<len;i++)a[i]=(ll)a[i]*t%mod;
    	}
    	struct poly
    	{
    		vector<int >a;int len;
    		poly(){a.clear();len=0;}
    		poly(int x){a.clear();a.push_back(x);len=1;}
    		void resize(int x){a.resize(x);for(int i=len;i<x;i++)a[i]=0;len=x;}
    		poly operator * (const poly &b) const 
    		{
    			poly c=poly();c.resize(len+b.len-1);
    			if(c.len<=200)
    			{
    				for(int i=0;i<len;i++)
    					for(int j=0;j<b.len;j++)
    						c.a[i+j]=(c.a[i+j]+(ll)a[i]*b.a[j])%mod;
    				return c;
    			}
    			int n=1,i;while(n<c.len)n<<=1;
    			for(i=0;i<len;i++)A[i]=a[i];for(i=len;i<n;i++)A[i]=0;
    			for(i=0;i<b.len;i++)B[i]=b.a[i];for(i=b.len;i<n;i++)B[i]=0;
    			NTT(A,n,1);NTT(B,n,1);for(i=0;i<n;i++)A[i]=(ll)A[i]*B[i]%mod;NTT(A,n,-1);
    			for(i=0;i<c.len;i++)c.a[i]=A[i];return c;
    		}
    		poly operator + (const poly &b) const
    		{
    			poly c=poly();c.resize(max(b.len,len));
    			for(int i=0;i<len;i++)c.a[i]=a[i];
    			for(int i=0;i<b.len;i++)(c.a[i]+=b.a[i])%=mod;
    			return c;
    		}
    		poly operator - (const poly &b) const
    		{
    			poly c=poly();c.resize(max(b.len,len));
    			for(int i=0;i<len;i++)c.a[i]=a[i];
    			for(int i=0;i<b.len;i++)(c.a[i]-=b.a[i])%=mod;
    			return c;
    		}
    		void get_inv(poly &b,int n)
    		{
    			if(n==1)return void(b=poly(inv(a[0])));get_inv(b,n>>1);int t=n<<1,lim=min(n,len);
    			for(int i=0;i<lim;i++)A[i]=a[i];for(int i=lim;i<t;i++)A[i]=0;
    			for(int i=0;i<b.len;i++)B[i]=b.a[i];for(int i=b.len;i<t;i++)B[i]=0;
    			NTT(A,t,1);NTT(B,t,1);for(int i=0;i<t;i++)B[i]=(2-(ll)A[i]*B[i]%mod)*B[i]%mod;NTT(B,t,-1);
    			b.resize(n);for(int i=0;i<n;i++)b.a[i]=B[i];
    		}
    		poly Dao(){poly c=poly();c.resize(len-1);for(int i=1;i<len;i++)c.a[i-1]=(ll)i*a[i]%mod;return c;}
    		poly Int(){poly c=poly();c.resize(len+1);for(int i=0;i<len;i++)c.a[i+1]=(ll)a[i]*inv(i+1)%mod;return c;}
    		poly get_ln(int n)
    		{
    			poly c=poly();get_inv(c,n);
    			c=(c*Dao()).Int();c.resize(n);
    			return c;
    		}
    		void print(){printf("lenth = %d
    ",len);for(int i=0;i<len;i++)printf("%d ",a[i]);puts("");}
    	}a,b;
    	void get_exp(const poly &a,poly &b,int len)
    	{
    		if(len==1){b=poly(1);return ;}get_exp(a,b,len>>1);
    		poly c=a-b.get_ln(len);c.a[0]++;c.resize(len);b=b*c;b.resize(len);
    	}
    	int fac[N],inv[N];
    	int main()
    	{
    		if(y==1)return printf("%d
    ",q_pow(n,2*(n-2))),0;
    		fac[0]=1;for(int i=1;i<=n;i++)fac[i]=(ll)fac[i-1]*i%mod;
    		inv[n]=inv(fac[n]);for(int i=n;i;i--)inv[i-1]=(ll)inv[i]*i%mod;
    		int K=(ll)n*n%mod*y%mod*inv(1-y)%mod;a.resize(n+1);
    		for(int i=1;i<=n;i++)a.a[i]=(ll)q_pow(i,i)*inv[i]%mod*K%mod;
    		int len=1;while(len<(n+1))len<<=1;get_exp(a,b,len);
    		printf("%lld
    ",((ll)b.a[n]*fac[n]%mod*q_pow(n,mod-5)%mod*q_pow(1-y,n)%mod+mod)%mod);
    		return 0;
    	}
    }
    

    然后完整代码如下:

    #include <cstdio>
    #include <algorithm>
    #include <cmath>
    #include <cstring>
    #include <cstdlib>
    #include <queue>
    #include <iostream>
    #include <bitset>
    #include <map>
    using namespace std;
    #define N 100005
    #define ll long long
    #define mod 998244353
    struct node{int to,next;}e[N<<1];
    int n,head[N],y,op,cnt;
    void add(int x,int y){e[cnt]=(node){y,head[x]};head[x]=cnt++;}
    int q_pow(int x,int n){int ret=1;for(;n;n>>=1,x=(ll)x*x%mod)if(n&1)ret=(ll)ret*x%mod;return ret;}
    #define inv(x) q_pow(x,mod-2)
    map<int ,int >mp[N];
    namespace Subtask1
    {
    	int main()
    	{
    		int cnt=n;
    		for(int x=1;x<=n;x++)
    		{
    			for(int i=head[x];i!=-1;i=e[i].next)
    			{
    				int to1=e[i].to;
    				if(to1<x&&mp[x].find(to1)!=mp[x].end())cnt--;
    			}
    		}
    		printf("%d
    ",q_pow(y,cnt));
    		return 0;
    	}
    }
    namespace Subtask2
    {
    	int f[N],g[N],K;
    	void dfs(int x,int from)
    	{
    		g[x]=K,f[x]=1;
    		for(int i=head[x];i!=-1;i=e[i].next)
    		{
    			int to1=e[i].to;
    			if(to1!=from)
    			{
    				dfs(to1,x);
    				g[x]=(g[x]+(ll)g[to1]*inv(g[to1]+f[to1]))%mod;
    				f[x]=(ll)f[x]*(g[to1]+f[to1])%mod;
    			}
    		}
    		g[x]=(ll)g[x]*f[x]%mod;
    	}
    	int main()
    	{
    		if(y==1)return printf("%d
    ",q_pow(n,n-2)),0;
    		K=(ll)n*y%mod*inv(1-y)%mod;dfs(1,0);
    		printf("%lld
    ",((ll)g[1]*q_pow(n,mod-3)%mod*q_pow(1-y,n)%mod+mod)%mod);
    		return 0;
    	}
    }
    namespace Subtask3
    {
    	int A[N<<2],B[N<<2];
    	void NTT(int *a,int len,int flag)
    	{
    		int i,j,k,t,w,x,tmp;
    		for(i=k=0;i<len;i++)
    		{
    			if(i>k)swap(a[i],a[k]);
    			for(j=len>>1;(k^=j)<j;j>>=1);
    		}
    		for(k=2;k<=len;k<<=1)
    		{
    			t=k>>1;x=q_pow(3,(mod-1)/k);if(flag==-1)x=inv(x);
    			for(i=0;i<len;i+=k)
    				for(w=1,j=i;j<i+t;j++,w=(ll)w*x%mod)
    					tmp=(ll)w*a[j+t]%mod,a[j+t]=(a[j]-tmp)%mod,a[j]=(a[j]+tmp)%mod;
    		}if(flag==-1)for(t=inv(len),i=0;i<len;i++)a[i]=(ll)a[i]*t%mod;
    	}
    	struct poly
    	{
    		vector<int >a;int len;
    		poly(){a.clear();len=0;}
    		poly(int x){a.clear();a.push_back(x);len=1;}
    		void resize(int x){a.resize(x);for(int i=len;i<x;i++)a[i]=0;len=x;}
    		poly operator * (const poly &b) const 
    		{
    			poly c=poly();c.resize(len+b.len-1);
    			if(c.len<=200)
    			{
    				for(int i=0;i<len;i++)
    					for(int j=0;j<b.len;j++)
    						c.a[i+j]=(c.a[i+j]+(ll)a[i]*b.a[j])%mod;
    				return c;
    			}
    			int n=1,i;while(n<c.len)n<<=1;
    			for(i=0;i<len;i++)A[i]=a[i];for(i=len;i<n;i++)A[i]=0;
    			for(i=0;i<b.len;i++)B[i]=b.a[i];for(i=b.len;i<n;i++)B[i]=0;
    			NTT(A,n,1);NTT(B,n,1);for(i=0;i<n;i++)A[i]=(ll)A[i]*B[i]%mod;NTT(A,n,-1);
    			for(i=0;i<c.len;i++)c.a[i]=A[i];return c;
    		}
    		poly operator + (const poly &b) const
    		{
    			poly c=poly();c.resize(max(b.len,len));
    			for(int i=0;i<len;i++)c.a[i]=a[i];
    			for(int i=0;i<b.len;i++)(c.a[i]+=b.a[i])%=mod;
    			return c;
    		}
    		poly operator - (const poly &b) const
    		{
    			poly c=poly();c.resize(max(b.len,len));
    			for(int i=0;i<len;i++)c.a[i]=a[i];
    			for(int i=0;i<b.len;i++)(c.a[i]-=b.a[i])%=mod;
    			return c;
    		}
    		void get_inv(poly &b,int n)
    		{
    			if(n==1)return void(b=poly(inv(a[0])));get_inv(b,n>>1);int t=n<<1,lim=min(n,len);
    			for(int i=0;i<lim;i++)A[i]=a[i];for(int i=lim;i<t;i++)A[i]=0;
    			for(int i=0;i<b.len;i++)B[i]=b.a[i];for(int i=b.len;i<t;i++)B[i]=0;
    			NTT(A,t,1);NTT(B,t,1);for(int i=0;i<t;i++)B[i]=(2-(ll)A[i]*B[i]%mod)*B[i]%mod;NTT(B,t,-1);
    			b.resize(n);for(int i=0;i<n;i++)b.a[i]=B[i];
    		}
    		poly Dao(){poly c=poly();c.resize(len-1);for(int i=1;i<len;i++)c.a[i-1]=(ll)i*a[i]%mod;return c;}
    		poly Int(){poly c=poly();c.resize(len+1);for(int i=0;i<len;i++)c.a[i+1]=(ll)a[i]*inv(i+1)%mod;return c;}
    		poly get_ln(int n)
    		{
    			poly c=poly();get_inv(c,n);
    			c=(c*Dao()).Int();c.resize(n);
    			return c;
    		}
    		void print(){printf("lenth = %d
    ",len);for(int i=0;i<len;i++)printf("%d ",a[i]);puts("");}
    	}a,b;
    	void get_exp(const poly &a,poly &b,int len)
    	{
    		if(len==1){b=poly(1);return ;}get_exp(a,b,len>>1);
    		poly c=a-b.get_ln(len);c.a[0]++;c.resize(len);b=b*c;b.resize(len);
    	}
    	int fac[N],inv[N];
    	int main()
    	{
    		if(y==1)return printf("%d
    ",q_pow(n,2*(n-2))),0;
    		fac[0]=1;for(int i=1;i<=n;i++)fac[i]=(ll)fac[i-1]*i%mod;
    		inv[n]=inv(fac[n]);for(int i=n;i;i--)inv[i-1]=(ll)inv[i]*i%mod;
    		int K=(ll)n*n%mod*y%mod*inv(1-y)%mod;a.resize(n+1);
    		for(int i=1;i<=n;i++)a.a[i]=(ll)q_pow(i,i)*inv[i]%mod*K%mod;
    		int len=1;while(len<(n+1))len<<=1;get_exp(a,b,len);
    		printf("%lld
    ",((ll)b.a[n]*fac[n]%mod*q_pow(n,mod-5)%mod*q_pow(1-y,n)%mod+mod)%mod);
    		return 0;
    	}
    }
    int main()
    {
    	scanf("%d%d%d",&n,&y,&op);
    	if(op==2)return Subtask3::main();memset(head,-1,sizeof(head));
    	for(int i=1,x,y;i<n;i++)scanf("%d%d",&x,&y),add(x,y),add(y,x);
    	if(op==1)return Subtask2::main();
    	for(int i=1,x,y;i<n;i++)scanf("%d%d",&x,&y),mp[x][y]=mp[y][x]=1;
    	if(op==0)return Subtask1::main();
    }
    

    如果知道这个题的Subtask 2是多项式exp还行,如果不知道的话,硬想很难的qwq

    然后Subtask 1的容斥才是本题的关键,Subtask 2算是锦上添花吧qwq

    总体上,本题考查了$min-max$容斥(你仔细看看容斥式子,就是这个

    考察了优化DP的一些技巧,同时搭配$prufer$序列的知识。

    最后还考察了多项式exp的巧妙转化

    是对选手数学功底的一个考验,显然对于我这种菜鸡就是挑战了qwq

    然后还有一些奇妙的性质,总之,这是一个非常优秀的计数题目

    最后,$Orz rqy$

  • 相关阅读:
    从0开始学习 GitHub 系列之「02.加入 GitHub」
    从0开始学习 GitHub 系列之「01.初识 GitHub
    用Redis轻松实现秒杀系统
    算法之美
    Android窗口管理服务WindowManagerService显示Activity组件的启动窗口(Starting Window)的过程分析
    6)django-示例(fbv)
    5)django-模板
    4)django-视图view
    3)django-路由系统url
    2)django-请求生命周期
  • 原文地址:https://www.cnblogs.com/Winniechen/p/10466316.html
Copyright © 2011-2022 走看看