首页 > 其他分享 >【WC2019】数树(prufer序列,树上连通块DP,多项式exp)

【WC2019】数树(prufer序列,树上连通块DP,多项式exp)

时间:2022-10-30 10:23:52浏览次数:46  
标签:连通 数树 int sum cap exp ans aligned WC2019

设两棵树的边集分别为 \(E_1,E_2\),那么两棵树不同当且仅当它们对应的边集不同。

转化一下可以发现,染色方案等于 \(y^{n-|E_1\cap E_2|}\),即由边集 \(E_1\cap E_2\) 构成的图的连通块数量。

\(k=1\)

\[ans=\sum_{E_2}y^{n-|E_1\cap E_2|} \]

考虑枚举 \(S=E_1\cap E_2\),那么我们需要知道有多少种不同的 \(E_2\)。

先不考虑算重的问题,我们考虑在 \(S\) 的基础上添加若干边使得图构成一棵树的方案数。设边集 \(S\) 构成了若干个大小分别为 \(s_1,s_2,\cdots,s_k\) 的连通块(显然它们都是树),根据扩展 Cayley 定理可知方案数为 \(n^{k-2}\prod_{i=1}^k s_i\),而且发现 \(k=n-|S|\)。

这样显然会算重,需要容斥,大胆假设容斥系数为 \(f(|S|)\):

\[ans=\sum_{S\subseteq E_1} f(|S|)n^{n-|S|-2}\prod_{i=1}^{n-|S|}s_i \]

考虑一种 \(E_2\) 在原式和容斥式中的贡献:(令 \(S=E_1\cap E_2\),\(y'=y^{-1}\))

\[\begin{aligned} y^{n-|S|}&=\sum_{T\subseteq S}f(|T|)\\ y'^{|S|}&=\sum_{|T|=0}^{|S|}\binom{|S|}{|T|}\dfrac{f(|T|)}{y^n} \end{aligned} \]

令 \(\dfrac{f(|T|)}{y^n}=(y'-1)^{|T|}\) 即可,解得 \(f(|T|)=(1-y)^{|T|}y^{n-|T|}\)。

于是:

\[\begin{aligned} ans&=\sum_{S\subseteq E_1}(1-y)^{|S|}y^{n-|S|}n^{n-|S|-2}\prod_{i=1}^{n-|S|}s_i\\ &=y^nn^{n-2}\sum_{S\subseteq E_1}(1-y)^{|S|}y^{-|S|}n^{-|S|}\prod_{i=1}^{n-|S|}s_i\\ \end{aligned} \]

这是树上连通块 DP 问题,可以设 \(f_{u,s}\) 表示考虑完 \(u\) 子树内的连通块情况,其中以 \(u\) 为根的连通块大小为 \(s\),所有方案的贡献和。直接转移是 \(O(n^2)\) 的。

神奇的是我们可以将 \(\prod\limits_{i=1}^{n-|S|}s_i\) 看成其组合意义:每个连通块内各选一个点的方案数。于是我们可以设 \(f_{u,0/1}\) 表示考虑完 \(u\) 子树内的连通块情况,其中以 \(u\) 为根的连通块是否已经选了点,所有方案的贡献和(此时的两种方案不同当且仅当连通块情况不同或连通块内选点情况不同)。那么转移就是 \(O(n)\) 的了。

\(k=2\)

\[ans=\sum_{E_1}\sum_{E_2}y^{n-|E_1\cap E_2|} \]

同样地考虑枚举 \(S=E_1\cap E_2\),设 \(g(S)\) 为在 \(S\) 的基础上添加若干边使得图构成一棵树的方案数,\(k=1\) 时已经算过了为 \(g(S)=n^{n-|S|-2}\prod\limits_{i=1}^{n-|S|}s_i\),其中边集 \(S\) 构成了若干个大小分别为 \(s_1,s_2,\cdots,s_{n-|S|}\) 的连通块。

同样大胆假设容斥系数 \(f(|S|)\):

\[ans=\sum_{S}f(|S|)g(S)^2 \]

考虑一种 \(E_1,E_2\) 在原式和容斥式中的贡献:(设 \(S=E_1\cap E_2\))

\[y^{n-|S|}=\sum_{T\subseteq S}f(|T|) \]

同样可以得到 \(f(|T|)=(1-y)^{|T|}y^{n-|T|}\)。于是:

\[\begin{aligned} ans&=\sum_{S}(1-y)^{|S|}y^{n-|S|}g(S)^2\\ &=y^nn^{2n-4}\sum_{S}(1-y)^{|S|}y^{-|S|}n^{-2|S|}\prod\limits_{i=1}^{n-|S|}s_i^2 \end{aligned} \]

\(S\) 不太好枚举,不如直接转为枚举 \(s_i\),即把 \(\{1,\cdots,n\}\) 分为若干个集合,每个大小为 \(s\) 的集合为一个连通块(且是树),有 \(s^{s-2}\) 种生成树方案,每种方案的贡献都是 \((1-y)^{s-1}y^{-(s-1)}n^{-2(s-1)}s^2\),于是这个连通块总贡献为 \((1-y)^{s-1}y^{-(s-1)}n^{-2(s-1)}s^s\)。

考虑其指数生成函数:

\[\begin{aligned} F(x)&=\sum\limits_{s\geq 1}\frac{1}{s!}(1-y)^{s-1}y^{-(s-1)}n^{-2(s-1)}s^sx^s\\ &=\sum\limits_{s\geq 0}\frac{1}{s!}(1-y)^{s}y^{-s}n^{-2s}(s+1)^sx^{s+1}\\ \end{aligned} \]

于是 \(ans=y^nn^{2n-4}[x^n]n!e^{F(x)}\),多项式 exp 即可,时间复杂度 \(O(n\log n)\)。

#include<bits/stdc++.h>

#define N 100010

using namespace std;

namespace modular
{
	const int mod=998244353;
	inline int add(int x,int y){return x+y>=mod?x+y-mod:x+y;}
	inline int dec(int x,int y){return x-y<0?x-y+mod:x-y;}
	inline int mul(int x,int y){return 1ll*x*y%mod;}
	inline void Add(int &x,int y){x=x+y>=mod?x+y-mod:x+y;}
	inline void Dec(int &x,int y){x=x-y<0?x-y+mod:x-y;}
	inline void Mul(int &x,int y){x=1ll*x*y%mod;}
}using namespace modular;

inline int poww(int a,int b)
{
	int ans=1;
	while(b)
	{
		if(b&1) ans=mul(ans,a);
		a=mul(a,a);
		b>>=1;
	}
	return ans;
}

inline int read()
{
	int x=0,f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9')
	{
		if(ch=='-') f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9')
	{
		x=(x<<1)+(x<<3)+(ch^'0');
		ch=getchar();
	}
	return x*f;
}

int n,y;

namespace sub0
{
	#define pii pair<int,int>
	#define mk(a,b) make_pair(a,b)
	set<pii>e;
	void main()
	{
		for(int i=1;i<n;i++)
		{
			int u=read(),v=read();
			if(u>v) swap(u,v);
			e.insert(mk(u,v));
		}
		int same=0;
		for(int i=1;i<n;i++)
		{
			int u=read(),v=read();
			if(u>v) swap(u,v);
			if(e.find(mk(u,v))!=e.end()) same++;
		}
		printf("%d\n",poww(y,n-same));
	}
	#undef pii
	#undef mk
}

namespace sub1
{
	int invy,invn,coef;
	int cnt,head[N],nxt[N<<1],to[N<<1];
	int f[N][2];
	void adde(int u,int v)
	{
		to[++cnt]=v;
		nxt[cnt]=head[u];
		head[u]=cnt;
	}
	void dfs(int u,int fa)
	{
		static int g[2];
		f[u][0]=f[u][1]=1;
		for(int i=head[u];i;i=nxt[i])
		{
			int v=to[i];
			if(v==fa) continue;
			dfs(v,u);
			g[0]=mul(f[u][0],f[v][1]);
			Add(g[0],mul(f[u][0],mul(f[v][0],coef)));
			g[1]=mul(f[u][1],f[v][1]);
			Add(g[1],mul(f[u][0],mul(f[v][1],coef)));
			Add(g[1],mul(f[u][1],mul(f[v][0],coef)));
			f[u][0]=g[0];
			f[u][1]=g[1];
		}
	}
	void main()
	{
		invy=poww(y,mod-2),invn=poww(n,mod-2);
		coef=mul(dec(1,y),mul(invy,invn));
		for(int i=1;i<n;i++)
		{
			int u=read(),v=read();
			adde(u,v),adde(v,u);
		}
		dfs(1,0);
		printf("%d\n",mul(mul(poww(y,n),poww(n,n-2)),f[1][1]));
	}
}

namespace sub2
{
	#define LN 20
	int fac[N<<3],ifac[N<<3];
	vector<int>w[LN][2];
	void init(int limit)
	{
		for(int bit=0,mid=1;mid<limit;bit++,mid<<=1)
		{
			int len=mid<<1;
			int gn=poww(3,(mod-1)/len);
			int ign=poww(gn,mod-2);
			int g=1,ig=1;
			for(int j=0;j<mid;j++,g=mul(g,gn),ig=mul(ig,ign))
				w[bit][0].push_back(g),w[bit][1].push_back(ig);
		}
	}
	void NTT(int *a,int limit,int opt)
	{
		static int rev[N<<3];
		opt=(opt<0);
		for(int i=0;i<limit;i++)
			rev[i]=(rev[i>>1]>>1)|((i&1)*(limit>>1));
		for(int i=0;i<limit;i++)
			if(i<rev[i]) swap(a[i],a[rev[i]]);
		for(int bit=0,mid=1;mid<limit;bit++,mid<<=1)
		{
			for(int i=0,len=mid<<1;i<limit;i+=len)
			{
				for(int j=0;j<mid;j++)
				{
					int x=a[i+j],y=mul(w[bit][opt][j],a[i+mid+j]);
					a[i+j]=add(x,y),a[i+mid+j]=dec(x,y);
				}
			}
		}
		if(opt)
		{
			int tmp=poww(limit,mod-2);
			for(int i=0;i<limit;i++)
				a[i]=mul(a[i],tmp);
		}
	}
	void getinv(int *f,int *g,int n)
	{
		static int ff[N<<3];
		g[0]=poww(f[0],mod-2);
		int now=2;
		for(;now<(n<<1);now<<=1)
		{
			int limit=now<<1;
			for(int i=0;i<now;i++) ff[i]=f[i];
			NTT(ff,limit,1),NTT(g,limit,1);
			for(int i=0;i<limit;i++)
				g[i]=mul(dec(2,mul(ff[i],g[i])),g[i]);
			NTT(g,limit,-1);
			for(int i=now;i<limit;i++) g[i]=0;
		}
		for(int i=n;i<now;i++) g[i]=0;
		for(int i=0;i<now;i++) ff[i]=0;
	}
	void getder(int *f,int *g,int n)
	{
		for(int i=1;i<n;i++) g[i-1]=mul(i,f[i]);
		g[n-1]=0;
	}
	void getint(int *f,int *g,int n)
	{
		for(int i=n-1;i>=1;i--) g[i]=mul(mul(ifac[i],fac[i-1]),f[i-1]);
		g[0]=0;
	}
	void getln(int *f,int *g,int n)
	{
		static int derf[N<<3],invf[N<<3];
		getder(f,derf,n);
		getinv(f,invf,n);
		int limit=1;
		while(limit<(n<<1)) limit<<=1;
		NTT(derf,limit,1),NTT(invf,limit,1);
		for(int i=0;i<limit;i++)
			derf[i]=mul(derf[i],invf[i]);
		NTT(derf,limit,-1);
		getint(derf,g,n);
		for(int i=0;i<limit;i++) derf[i]=invf[i]=0;
	}
	void getexp(int *f,int *g,int n)
	{
		static int ff[N<<3],lng[N<<3];
		assert(!f[0]);
		g[0]=1;
		int now=2;
		for(;now<(n<<1);now<<=1)
		{
			int limit=now<<1;
			getln(g,lng,now);
			for(int i=0;i<now;i++) ff[i]=f[i];
			for(int i=0;i<now;i++) Dec(ff[i],lng[i]);
			Add(ff[0],1);
			NTT(g,limit,1),NTT(ff,limit,1);
			for(int i=0;i<limit;i++)
				g[i]=mul(g[i],ff[i]);
			NTT(g,limit,-1);
			for(int i=now;i<limit;i++) g[i]=0;
		}
		for(int i=n;i<now;i++) g[i]=0;
		for(int i=0;i<now;i++) lng[i]=ff[i]=0;
	}
	void main()
	{
		int limit=1;
		while(limit<=(n<<2)) limit<<=1;
		init(limit);
		fac[0]=1;
		for(int i=1;i<=(n<<3);i++) fac[i]=mul(fac[i-1],i);
		ifac[n<<3]=poww(fac[n<<3],mod-2);
		for(int i=(n<<3);i>=1;i--) ifac[i-1]=mul(ifac[i],i);
		static int f[N<<3],g[N<<3];
		int coef=1,tmp=mul(mul(dec(1,y),poww(y,mod-2)),poww(mul(n,n),mod-2));
		for(int i=0;i<n;i++)
		{
			f[i+1]=mul(mul(ifac[i],coef),poww(i+1,i));
			Mul(coef,tmp);
		}
		getexp(f,g,n+1);
		printf("%d\n",mul(mul(poww(y,n),poww(n,2*n-4)),mul(g[n],fac[n])));
	}
	#undef LN
}

int main()
{
	n=read(),y=read();int opt=read();
	if(!opt) sub0::main();
	else if(opt==1) sub1::main();
	else sub2::main();
	return 0;
}

标签:连通,数树,int,sum,cap,exp,ans,aligned,WC2019
From: https://www.cnblogs.com/ez-lcw/p/16840589.html

相关文章

  • Koa与Express的区别
    Koa与Express的区别glory苦逼前端​关注他 8人赞同了该文章框架介绍express框架是一个基于Node.js平台的极简、灵活的web应用开发框架,主要......
  • 快读《ASP.NET Core技术内幕与项目实战》EFCore2.6:表达式树Expression和Func
    本节内容,涉及5.3(P142-P153)。主要NuGet包:ExpressionTreeToString(用于以string格式,输出表达式树)System.Linq.Dynamic.Core(通过字符串形式,非常简单的构建表达式树,可以不用......
  • 如何通过node.js(express)写接口
    准备工作:在开始项目之前确保自己的计算机已经安装了 Node.js【node.js下载地址:https://nodejs.org/en/】一、创建项目打开文件夹,在想要的位置为项目新建一个目录文件,c......
  • DevExpress各组件dll说明
    DevExpress各组件dll说明:“C:\ProgramFiles(x86)\DevExpress18.1\Components\Bin\Framework\“Cross-PlatformAssembliesDescriptionDevExpress.Charts.v18.1......
  • GeckoFx (4)使用 treeview 展示 dom 数树
    GeckoFx(4)使用treeview展示dom数树使用DocumentCompleted事件,在页面加载完成后构建一个dom树使用treeview控件。treeHtml:treeview......
  • 07-项目训练_流程与快递表格创建~Express Service编写
    目录​​一,主要流程​​​​二,编码过程​​​​1,创建数据库表格​​​​2, API设计​​​​3,DAO接口编写​​​​4,SQL语句编写​​​​4.1向Express表添加记录​​​​4.2......
  • 6.CF431E Chemistry Experiment 权值线段树+二分
    6.CF431EChemistryExperiment权值线段树+二分给定数列,区间查询和,区间取模,单点修改。记录区间最大值,对于区间最大值小于模数的区间不予更新洛谷传送门:​​CF431EChemist......
  • 如何用界面组件DevExpress WPF创建Excel式的过滤功能?赶紧Get
    DevExpressWPF拥有120+个控件和库,将帮助您交付满足甚至超出企业需求的高性能业务应用程序。通过DevExpressWPF能创建有着强大互动功能的XAML基础应用程序,这些应用程序专......
  • 「MySQL高级篇」explain分析SQL,索引失效&&常见优化场景
    大家好,我是melo,一名大三后台练习生专栏回顾索引的原理&&设计原则欢迎关注本专栏:MySQL高级篇本篇速览在我们上一篇文章中,讲到了索引的原理&&设计原则,知道了索引如......
  • MySQL组合索引(a,b,c)和EXPLAIN的使用
    前言MySQL索引的建立对于MySQL的高效运行是很重要的,索引可以大大提高MySQL的检索速度。而索引可分单列索引和组合索引。单列索引,即一个索引只包含单个列,一个表可以有多......