zoukankan      html  css  js  c++  java
  • 洛谷 P4708

    洛谷题面传送门

    神仙题 %%%%%%%%%%%%%%%%%%%%

    题解搬运人来了

    首先看到本质不同(无标号)的图计数咱们可以想到 Burnside 引理,具体来说,我们枚举一个排列 (p),并统计有多少张图中的点集在置换 (p) 的作用下能够保持不变,记这个数目为 (c(p)),那么答案就是 (dfrac{1}{n!}sumlimits_{p}c(p))。由于此题 (n) 高达 (50),因此暴力枚举 (p) 显然是不合理的,不过注意到合法的图的数量并不取决于 (p) 具体每个元素是什么,而取决于 (p) 中每个置换环的大小分别是多少,而 (50) 的整数拆分数量并不是很多,因此考虑枚举 (p) 中置换环的大小,设为 (s_1,s_2,s_3,cdots,s_k),其中 (s_1<s_2<cdots<s_k),那么符合条件的排列 (p) 的个数可以这样计算:

    • 首先,对于所有置换环选出属于该置换环的 (s_i) 个点,这显然是一个多重组合数的形式,方案数为 (dfrac{n!}{prodlimits_{i=1}^ks_i!})
    • 其次,每个置换环,根据圆排列的计数方法,将其进行排列有 ((s_i-1)!)​ 种方案,因此还需乘上个 (prodlimits_{i=1}^k(s_i-1)!)
    • 再其次,所有大小相同的置换环是视作相同的,因此假设大小为 (x)​ 的置换环有 (cnt_x)​ 个,那么答案还需除以 (prodlimits_{i=1}^{s_k}cnt_i!)

    因此对于一组大小分别为 (s_1,s_2,cdots,s_k)​​ 的置换环,有 (dfrac{n!}{prodlimits_{i=1}^ks_i!prodlimits_{i=1}^{s_k}cnt_i!}·prodlimits_{i=1}^k(s_i-1)!)​​ 个排列 (p),满足其置换环的大小组成的集合恰好是 ({s_1,s_2,cdots,s_k})

    接下来考虑如何计算对于一个置换环大小分别为 (s_1,s_2,cdots,s_k) 的置换 (p)​,有多少张图满足其在 (p) 的作用下能够保持同构且每个连通块都有欧拉回路,即每个点的度都是偶数。考虑分两部分处理:

    1. 每个置换环内部点与点之间的边
    2. 置换环与置换环之间的边

    首先对于第一部分而言,假设我们考虑第 (i)​​ 个置换环,那么显然图在 (p)​​ 的作用下保持同构的必要条件是第 (i)​​ 个置换环中所有距离相同的边存在/不存在的状态都相同,我们先不考虑每个点度都要是偶数这个条件,那么显然有 (2^{lfloordfrac{s_i}{2} floor})​​ 种连边的情况,接下来考虑加上每个点度都要是偶数这个条件后怎么处理,不难发现,对于一般的情况,距离为 (d)​​ 的边都连上是不影响这个连通块中所有点的奇偶性的,唯独一种情况——(d=dfrac{s_i}{2})​​,且 (s_i)​​ 为偶数的情况,这种情况下,这个置换环中所有点度数的奇偶性都会反转,也就是说对于 (s_i)​​ 是偶数的情况,选择另外 (d)​ 种距离的方案数为 (2^{lfloordfrac{s_i}{2} floor-1})​ 种,而对于 (d=dfrac{s_i}{2})​​ 的情况,相当于我们给了这个连通块一次改变所有点奇偶性的机会,可选可不选。

    接下来考虑不同置换环之间连边的方案,我们假设现在考虑了编号为 (i,j)​ 的两个置换环,那么考虑将两个置换环上的点顺次编号 (0,1,2,3,cdots,s_i-1)​ 以及 (0,1,2,3,cdots,s_j-1)​。考虑记 (d=gcd(s_i,s_j))​,那么显然,图在 (p)​ 的作用下保持同构的必要条件是对于第 (i)​ 个置换环上的点 (x)​ 和第 (j)​ 个置换环上的点 (y)​ 而言,将 ((x,y))​ 沿着置换环旋转得到的所有点对的连边状况都与 ((x,y))​ 的连边状况相同,而对于所有 ((x,y))​,转 (dfrac{s_is_j}{gcd(s_i,s_j)}= ext{lcm}(s_i,s_j))​ 步就能转回原位,因此有 (dfrac{s_is_j}{s_is_j/gcd(s_i,s_j)}=gcd(s_i,s_j)=d)​ 个本质不同的二元组。接下来考虑每个点度必须要是偶数这个条件,显然对于第 (i)​ 个置换环上的每个点 (x)​,包含 (x)​ 的二元组恰有 (s_j)​ 个,而这当中又会有 (d)​ 组二元组能够通过旋转得到,因此将这 (d)​ 个本质不同的二元组中任意一组连上,都会导致第 (i)​ 个置换环上每一个点多连出 (dfrac{s_j}{d})​ 条边,同理也会使第 (j)​ 个置换环上每一个点多连出(dfrac{s_i}{d})​ 条边。记 (x_1=dfrac{s_j}{d},x_2=dfrac{s_i}{d}),现在考虑分情况讨论:

    • 如果 (x_1,x_2) 都是偶数,那么显然这 (d) 个二元组不对两个置换环中任何一个点的度数的奇偶性产生影响,也就是说这 (d) 组边连与不连皆可,方案数乘上 (2^d)
    • 如果 (x_1)​ 为偶数,(x_2)​ 为奇数,那么连上这 (d)​ 个二元组中的一个不对第 (i)​ 个置换环中任何一个点的度数的奇偶性产生影响,但会改变第 (j)​ 个连通块中所有点的奇偶性,也就相当于我们给了第 (j)​ 个连通块 (d)​​ 次改变所有点奇偶性的机会。
    • 如果 (x_1)​ 为奇数,(x_2)​ 为偶数,类似于上面的情况,不过这次是我们给第 (i)​ 个连通块 (d)​ 次改变所有点奇偶性的机会
    • 如果 (x_1,x_2) 都是奇数,那么连上这 (d) 个二元组中的一个会同时改变第 (i) 个和第 (j) 个置换环中所有点度数的奇偶性,也就是说给了 (d)同时改变置换环 (i,j)​ 所有点奇偶性的机会。

    这样问题就转化为这样一个问题

    有一张 (k) 个点的图,点上有点权,初始每个点点权为 (0),每个点 (i) 上有 (cp_i) 个不同的开关,拉上这 (cp_i) 个开关中的任何一个都会使第 (i) 个点的点权异或 (1);每条边 ((i,j)) 上有 (ce_{i,j}) 个不同的开关,拉上这 (ce_{i,j}) 个开关中的任何一个都会使 (i,j) 的点权同时异或 (1),问有多少个拉开关的集合,满足拉上这些开关,能够使每个点点权都是 (0)

    这里有一个性质,就是对于所有连通块而言:

    • 我们考虑找出连通块的一棵 DFS 树(这里 DFS 树的定义为,将所有点对 ((i,j)) 之间连上 (ce_{i,j}) 条边后图的 DFS 树,也就是如果两点间有多条边那么最多只能有一条边在 DFS 树上),如果我们确定了所有非树边的状态,那么树边的状态已经唯一确定了,具体方案就是从叶子开始,如果叶子权值为 (1) 就拉上与叶子相连那条边上的开关,否则不拉,然后删去那个叶子。
    • 能够将连通块中所有点点权变为 (0)​ 还有一个必要条件,就是对点的操作的数量必须是偶数,因为对边的操作不会改变连通块中所有点权的异或和。

    也就是说假设 (S_p) 为这个连通块中所有点 (cp) 之和,(S_e) 为这个连通块两两之间 (ce) 之和,(S) 为该连通块的点数,那么方案数就是

    [2^{max(S_p-1,0)+S_e-(S-1)} ]

    并查集维护一下即可。

    时间复杂度 (mathcal O( ext{Can pass})).

    const int MAXN=50;
    const int MOD=998244353;
    int n,fac[MAXN+5],ifac[MAXN+5],pw2[MAXN*MAXN*MAXN+5];
    int g[MAXN+5][MAXN+5];
    void init_fac(){
    	for(int i=(pw2[0]=fac[0]=ifac[0]=ifac[1]=1)+1;i<=MAXN;i++) ifac[i]=1ll*ifac[MOD%i]*(MOD-MOD/i)%MOD;
    	for(int i=1;i<=MAXN;i++) fac[i]=1ll*fac[i-1]*i%MOD,ifac[i]=1ll*ifac[i-1]*ifac[i]%MOD;
    	for(int i=1;i<=MAXN*MAXN*MAXN;i++) pw2[i]=(pw2[i-1]<<1)%MOD;
    }
    int s[MAXN+5],cnt[MAXN+5],ans=0;
    int f[MAXN+5],c1[MAXN+5],c2[MAXN+5],siz[MAXN+5];
    int find(int x){return (!f[x])?x:f[x]=find(f[x]);}
    void merge(int x,int y,int z){
    	x=find(x);y=find(y);
    	if(x==y) c2[x]+=z;
    	else{
    		if(siz[x]<siz[y]) swap(x,y);
    		f[y]=x;siz[x]+=siz[y];
    		c1[x]+=c1[y];c2[x]+=c2[y];
    		c2[x]+=z;
    	}
    }
    int calc(int k){
    	int res=1;
    	for(int i=1;i<=k;i++) res=1ll*res*ifac[s[i]]%MOD*fac[s[i]-1]%MOD;
    	for(int i=1;i<=n;i++) res=1ll*res*ifac[cnt[i]]%MOD;
    	memset(f,0,sizeof(f));memset(c1,0,sizeof(c1));memset(c2,0,sizeof(c2));
    	for(int i=1;i<=k;i++) siz[i]=1;
    	for(int i=1;i<=k;i++){
    		res=1ll*res*pw2[s[i]-1>>1]%MOD;
    		if(~s[i]&1) c1[i]++;
    	}
    	for(int i=1;i<=k;i++) for(int j=i+1;j<=k;j++){
    		int d=g[s[i]][s[j]];
    		int d1=s[j]/d,d2=s[i]/d;
    		if((~d1&1)&&(~d2&1)) res=1ll*res*pw2[d]%MOD;
    		else if((d1&1)&&(d2&1)) merge(i,j,d);
    		else if(d1&1) c1[find(i)]+=d;
    		else c1[find(j)]+=d;
    	}
    	for(int i=1;i<=k;i++) if(find(i)==i){
    		res=1ll*res*pw2[max(c1[i]-1,0)+c2[i]-siz[i]+1]%MOD;
    	}
    //	for(int i=1;i<=k;i++) printf("%d%c",s[i]," 
    "[i==k]);
    //	printf("%d
    ",res);
    	return res;
    }
    void dfs(int x,int sum,int pre){
    	if(sum==n) return ans=(ans+calc(x-1))%MOD,void();
    	for(int i=pre;sum+i<=n;i++){
    		s[x]=i;cnt[i]++;dfs(x+1,sum+i,i);cnt[i]--;
    	}
    }
    int main(){
    	scanf("%d",&n);init_fac();
    	for(int i=1;i<=n;i++) for(int j=1;j<=n;j++)
    		g[i][j]=__gcd(i,j);
    	dfs(1,0,1);printf("%d
    ",ans);
    	return 0;
    }
    
  • 相关阅读:
    【转】myeclipse设置优化+快捷命令大全
    记昨日参加南天竺饶老师回访的一些感触点
    [zz]程序猿,你今天装B了没?
    什么是CGI
    Agile Tour——敏捷,在厦门落地 笔记小结
    用按键精灵来自动投票
    win7下安装matlab,启动后提示VC++Runtime Library错误 runtime error!
    单次扫描完成二值图连通区域标记
    6.3.2 最小支撑树树Prim算法,基于优先队列的Prim算法,Kruskal算法,Boruvka算法,“等价类”UnionFind
    用Java HashMap做对象Cache时要注意一点
  • 原文地址:https://www.cnblogs.com/ET2006/p/luogu-P4708.html
Copyright © 2011-2022 走看看