[UOJ#498]新年的追逐战

XXXVI.[UOJ#498]新年的追逐战

考虑最simple的场景,即我们要计算的是两张图的乘积 G=G1×G2。显然,G 中的两个点 (u1,u2)(v1,v2) 联通,当且仅当存在两条长度相等的可以是非简单的路径,满足第一条在 G1 中且端点分别是 u1,v1,第二条在 G2 中且端点是 u2,v2。显然,只要找得到这么两条路径,则路径上所有边都会出现在 G 中,也即它们联通。

很显然的是,我们可以对 G1G2 的每个连通块分开考虑。于是我们接下来只需设 G1G2 分别都是连通图,计算此时 G 中连通块个数即可。

因为我们可以在一条路径上反复横跳使路径长度不断 +2,致使只要 u1,u2v1,v2 间各存在一条奇偶性相同的路径,它们就能联通。在没有奇环的图中,两点间路径长度奇偶性唯一;但是在有奇环的图中,只需绕奇环走一圈即可改变路径长度奇偶性,因而任意两点间就会同时存在奇路径与偶路径。于是我们只需考虑 G1,G2 是否是二分图。

  • G1,G2 均不是二分图。此时,显然 G 会是一个非二分图的连通图,因为任意两点均联通。
  • G1,G2 恰有一个是二分图(不妨设为 G2)。此时,G1 中任一点与 G2 中白点构成的点对,只会与 G1 中点与 G2 中黑点构成的点对连边,也即产生的结果还会是二分图。则 G 是一个二分图连通图。
  • G1,G2 均是二分图。此时显然会产生两个二分图连通图。

因此我们对于一张图,仅需记录其中非二分图连通块个数及二分图连通块个数,就能唯一刻画这张图在转移时的表现……吗?

我们似乎少考虑了什么?假如其中一个点是孤立点呢?孤立点不就找不出路径了吗?

因此我们发现,任意图 G 与孤立点的积,均会得到 |G| 个孤立点。这也意味着,在 n 张图的积中,仅有那些在每次乘法中均是两个非孤立点之积的部分才会不是孤立点。于是,设 ai 为第 i 张图点数,bi 为第 i 张图非孤立点数,则孤立点的贡献即为 aibi

现在,我们考虑求出对于一张 a 个点的图,其中:

  • 孤立点个数
  • 连通块个数
  • 二分图连通块个数

先考虑第二问。显然,a 个点的有标号任意图计数的EGF是 f(x)=i2(i2)xii!。依据我们上一题中的结论,若 a 个点有标号连通图计数的EGF是 g,则 expg=f,也即 g=lnf。要处理有标号连通块计数,就考虑其中所有连通块的方案数乘上其余节点所在任意图方案数,也即 gexpg,即 flnf,即为有标号连通块计数。

现在我们考虑第三问。显然,a 个点的有标号任意黑白染色二分图计数的EGF是 g(x)=ixii!j=0i(ij)2j(ij)(注意这里的 g 与上文 g 不同,这里已经重新定义了一个 g),其中 j 这重循环意为枚举左部节点个数,乘上二项式系数即为左部节点方案,而后面的 2 的次幂即为连边方案。则 lng2 即为有标号联通二分图计数的EGF,其中除以 2 指不区分左右部。则 flng2 即为有标号二分图连通块个数计数的EGF,其中 f 即为第二问中的 f

考虑如何求出 g。显然其可以化成 g(x)=ixij=0i1j!1(ij)!2j(ij)。但是解决问题的关键在于 j(ij)=(i2)(j2)(ij2),具体可以拆开得证。于是 g(x)=ixi2(i2)j=0i1j!(j2)×1(ij)!(ij2)。后面一大坨就可以卷积了。

我们最后来考虑最简单的第一问。a 个点的孤立点计数直接用 fx 即可,因为 x 是有标号孤立点计数(仅有点数为 1 的图才可能为孤立点)。

需要注意的是,无论是第二问中的 flnf,还是第三维中的 flng2,都还要减去 fx,因为我们本题的二分图与连通块都不能考虑孤立点。

又需要注意的是,这里的所有东西都是EGF,要记得乘上 i! 才是OGF。

现在考虑统计答案。设 (a,b) 表示图 G 的连通块个数、二分图连通块个数。则 G1×G2=(a1b1+a2b2,a2b1+a1b2)。注意到这里的式子与我们一开始的式子不同,因为一开始是区分二分图与非二分图的,但是二者可以并在一起成为连通块,并且式子便成为了更简便的上式。

可以直接转移,也可以注意到上式是异或卷积的形式而手动FWT优化,也不会有太大的差异。

时间复杂度 O(nlogn)

代码:

#include<bits/stdc++.h>
using namespace std;
const int mod=998244353;
const int PHI=998244352;
const int inv2=499122177;
int ksm(int x,int y=mod-2){int z=1;for(;y;y>>=1,x=1ll*x*x%mod)if(y&1)z=1ll*z*x%mod;return z;}
int n,a[100100],m,res;
namespace Poly{
	const int N=1<<20;
	const int G=3;
	int A[N],B[N],C[N],D[N],rev[N],lim,invlim;
	void NTT(int*a,int tp){
		for(int i=0;i<lim;i++)if(rev[i]>i)swap(a[i],a[rev[i]]);
		for(int md=1;md<lim;md<<=1){
			int rt=ksm(G,(mod-1)/(md<<1));if(tp==-1)rt=ksm(rt);
			for(int stp=md<<1,pos=0;pos<lim;pos+=stp)for(int i=0,w=1;i<md;i++,w=1ll*w*rt%mod){
				int x=a[pos+i],y=1ll*a[pos+md+i]*w%mod;
				a[pos+i]=(x+y)%mod,a[pos+md+i]=(x+mod-y)%mod;
			} 
		}
		if(tp==-1)for(int i=0;i<lim;i++)a[i]=1ll*a[i]*invlim%mod;
	} 
	void mul(int*a,int*b,int*c,int LG){
		invlim=ksm(lim=1<<LG);for(int i=0;i<lim;i++)rev[i]=(rev[i>>1]>>1)|((i&1)<<(LG-1));
		for(int i=0;i<lim;i++)A[i]=B[i]=0;
		for(int i=0;i<(lim>>1);i++)A[i]=a[i],B[i]=b[i];
//			for(int i=0;i<(lim>>1);i++)printf("%d ",a[i]);putchar('*');
//			for(int i=0;i<(lim>>1);i++)printf("%d ",b[i]);putchar('=');
		NTT(A,1),NTT(B,1);
		for(int i=0;i<lim;i++)A[i]=1ll*A[i]*B[i]%mod;
		NTT(A,-1);
		for(int i=0;i<lim;i++)c[i]=A[i];
//			for(int i=0;i<lim;i++)printf("%d ",c[i]);putchar('\n');
	}
	void inv(int *a,int *b,int LG){
		b[0]=ksm(a[0]);
		for(int k=1;k<=LG+1;k++){
			mul(b,a,C,k);
			for(int i=0;i<(1<<k);i++)C[i]=(mod-C[i])%mod;
			(C[0]+=2)%=mod;
			mul(C,b,b,k);
		}
	}
	void diff(int *a,int *b,int len){for(int i=0;i<len;i++)b[i]=1ll*a[i+1]*(i+1)%mod;b[len-1]=0;}
	void inte(int *a,int *b,int len){for(int i=len-1;i;i--)b[i]=1ll*a[i-1]*ksm(i)%mod;b[0]=0;}
	void ln(int *a,int *b,int LG){
		inv(a,b,LG);
		diff(a,C,1<<LG);
		mul(b,C,b,LG+1);
		inte(b,b,1<<LG);
	}
	void exp(int *a,int *b,int LG){
		b[0]=1;
		for(int k=1;k<=LG+1;k++){
			ln(b,D,k-1);
			for(int i=0;i<(1<<(k-1));i++)D[i]=(a[i]-D[i]+mod)%mod;
			D[0]=(D[0]+1)%mod;
			mul(b,D,b,k);
		}
	}
}
using namespace Poly;
int fac[N],caf[N];
int f[N],g[N],h[N],d[N],LG;
int main(){
	scanf("%d",&n);for(int i=1;i<=n;i++)scanf("%d",&a[i]),m=max(a[i],m);
	while((1<<LG)<=m)LG++;
	fac[0]=1;for(int i=1;i<=m;i++)fac[i]=1ll*fac[i-1]*i%mod;
	caf[m]=ksm(fac[m]);for(int i=m;i;i--)caf[i-1]=1ll*caf[i]*i%mod;
	for(int i=0;i<=m;i++)f[i]=1ll*ksm(2,(1ll*i*(i-1)/2)%PHI)*caf[i]%mod;
	ln(f,g,LG),mul(f,g,g,LG+1);
	for(int i=0;i<=m;i++)h[i]=1ll*ksm(2,PHI-(1ll*i*(i-1)/2)%PHI)*caf[i]%mod;
	
	mul(h,h,h,LG+1);
	for(int i=0;i<=m;i++)h[i]=1ll*ksm(2,(1ll*i*(i-1)/2)%PHI)*h[i]%mod;
	for(int i=m+1;i<(1<<(LG+1));i++)h[i]=0;
	ln(h,d,LG);
	for(int i=0;i<=m;i++)d[i]=1ll*d[i]*inv2%mod;
	mul(f,d,h,LG+1);
	
	for(int i=0;i<=m;i++)f[i]=1ll*f[i]*fac[i]%mod,g[i]=1ll*g[i]*fac[i]%mod,h[i]=1ll*h[i]*fac[i]%mod;
	for(int i=1;i<=m;i++)(g[i]+=mod-1ll*f[i-1]*i%mod)%=mod,(h[i]+=mod-1ll*f[i-1]*i%mod)%=mod;
//	for(int i=0;i<=m;i++)printf("%d %d %d\n",f[i],g[i],h[i]);
	int prod=1;
	for(int i=1;i<=n;i++)prod=1ll*prod*f[a[i]]%mod*a[i]%mod;
	res=prod,prod=1;
	for(int i=1;i<=n;i++)prod=1ll*prod*(f[a[i]]+mod-f[a[i]-1])%mod*a[i]%mod;
	(res+=mod-prod)%=mod,prod=inv2;
	for(int i=1;i<=n;i++)prod=1ll*prod*(g[a[i]]+h[a[i]])%mod;
	(res+=prod)%=mod,prod=inv2;
	for(int i=1;i<=n;i++)prod=1ll*prod*(g[a[i]]+mod-h[a[i]])%mod;
	(res+=prod)%=mod;
	printf("%d\n",res);
	return 0;
}
posted @   Troverld  阅读(96)  评论(0编辑  收藏  举报
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?
点击右上角即可分享
微信分享提示