10月杂题

一. CF1477D Nezzar and Hidden Permutations

很吊的一个题!完全没见过这种技巧!有没有一种可能只是我太菜了!

考虑一种和所有其他点相连的点。这些点的拓扑序是确定的。

考虑剩下的图,每个点度数 \(\le n-2\) 。那么它的补图满足没有孤立点,那么每个连通块大小 \(>1\)

考虑 菊花剖分 。我们取补图的一个生成森林,划分成若干个大小至少为 \(2\) 的菊花。

这个生成森林怎么取呢?就直接 dfs ,遍历补图的边,就枚举所有没访问过的点,判断是否与 \(u\) 相连,不是的话就从遍历下去,用 set 实现即可。

现在回到原图上,实际上不同的菊花间的边就不重要了:我们随意确定菊花间的大小关系,边就按这个关系连。

对于菊花内的边,我们考虑原图会长成什么样。在这个子图里,菊花中间的那个点 \(x\) 就一定孤立了。

于是我们对于菊花内的其他点,随意确定一个顺序,然后 \(p\) 先填 \(x\) ,再填边上的点;\(q\) 先填边上的点,再填 \(x\)

所以剩下的问题就是把树给剖成若干菊花了。

这个怎么搞呢,我们直接 dfs 一遍树。遍历到一个点 \(u\) ,已经属于某个菊花就不管;否则分两种情况:

①与 \(u\) 相连的点 \(v\) 中存在没有被覆盖的点。那就以 \(u\) 为中心,把所有与之相连的没被覆盖的 \(v\) 取了。

\(v\) 全都被取了。那我们任取一个 \(v\) ,然后这里分三种处理:

  1. \(v\) 就是所在菊花中心,那直接把 \(u\) 接上去。

  2. \(v\) 不是中心,所在的菊花大小 \(=2\) 。那么我们把 \(v\) 直接变成中心,接 \(u\) 即可。

  3. \(v\) 不是中心,所在的菊花大小 \(>2\) 。那么把 \(v\) 从它所在的菊花中分离出来,把 \(u,v\) 搞成一个菊花即可。

这个题就做完了。

二.CF1450G Communism

难度中等的一个题!算是自己在床上独立会了!

这个一种字母全部变成另一种字母的过程,不难想到建树,如果 \(x\) 合并到 \(y\) ,就对应于 \(x->y\) 的一个边。

然后一个子树能接到其他点上面,就得满足题意的这个 \(k(R-L+1)\le S\) 的限制。

我们就令 \(dp(s)\) 表示 \(s\) 这个集合能不能弄成一个森林,并且每一个子树都符合限制。

那么就有简单的 \(dp\) 转移:

  1. \(s\) 就是一棵树。枚举一个根 \(x\) ,看 \(f(s-x)\) 是否为 \(1\) 即可。

  2. \(s\) 是森林,枚举第一棵树的集合 \(t\) ,看 \(f(t)\)\(f(s-t)\) 是否为 \(1\) 即可。

复杂度 \(O(3^n)\) ,显然过不了。

于是我们需要考察这个限制具有什么性质。

\((a,b)\) 表示一个集合 \(a=S\)\(b=R-L+1\)

我们想考察两个集合 \((a,b)\) \((c,d)\) ,如果他们合并得到 \((a+c,e)\) ,那通过小学奥数的直觉可知,如果 \(e<b+d\) ,那么 \((a+c,e)\) 一定能满足限制。

然后 \(e<b+d\) 这个东西等同于是 \((a,b)\)\((c,d)\) 对应的区间有交。

所以我们转移的时候,考虑 \(S\) 这个森林,假如两棵树有交,那我们一定把它们能合并在一起。

也就是说,如果把所有字母按 \(L\) 从小到大排序并重编号,我们这个森林一定是 \(S\) 在编号上被划分成了若干区间。

说了这么多,目的就是转移的时候,我们只需枚举一个前缀即可。

复杂度 \(O(n2^n)\)

三.ARC151E Keep Being Substring

写得挺舒服,题解在 ARC 151 总结里了。

四. P6805 [CEOI2020] 春季大扫除

考虑一个单次询问 \(O(n)\) 的暴力怎么搞。我觉得这是一个“树上贪心”的典范:尽量在 lca 处匹配,但同时要覆盖所有边。

所以 \(siz_x\) 为偶数时上传两个,否则上传一个。

我们考虑修改。显然这个能用树剖/虚树解决。

五. baekjoon 18480 Four elements

题意:给你 \(n\) 个区间 \([l_i,r_i]\) ,保证两两不交,令 \(S\) 是这 \(n\) 个区间的并。

你要在 \(S\) 中取出 \(4\) 个互不相同的数,使得和为给定的 \(m\) ,求方案数。

\(n\le 400\)

做法:为了方便表达,考虑 \(F(x)\)\(S\) 的生成函数。

通过“互不相等”容斥可以得到答案为:

\([x^m]\frac{1}{24}*(F^4(x)-6F(x^2)F^2(x)+3F^2(x^2)+8F(x^3)F(x)-6F(x^4))\)

我们对于每一项分别考虑。

1’ \(F(x^4)\) 。直接判断 \(\frac{m}{4}\) 是否属于 \(S\)

2’ \(F^2(x^2)\)\(F(x^3)F(x)\) 。直接 \(n^2\) 枚举两个区间,进行数学运算。

3’ \(F^4(x)\)\(F(x^2)F^2(x)\)

考虑 \(G(x)=F^2(x)\) ,这个可以 \(O(n^2\log n)\) 求出(离散化复杂度),系数形如 \(O(n^2)\) 段一次函数。

则求 \(G^2(x)\)\(F(x^2)G(x)\) 。我们不能直接枚举,但不难发现能双指针实现,只有 \(O(n^2)\) 对区间会对有答案贡献。此处数学计算略复杂。

实现起来有一些细节, 具体可以参考代码。

view code
#include<bits/stdc++.h>
using namespace std;
const int mod=998244353,inv2=(mod+1)/2,inv6=(mod+1)/6;
int qpow(int a,int b){
	int c=1;
	for(;b;b>>=1){
		if(b&1)c=1ll*a*c%mod;
		a=1ll*a*a%mod;
	}
	return c;
}
int n,L[410],s,R[410];
int qc[3010000],cn,l[3010000],r[3010000],k[3010000],b[3001000];
int F(int x){return lower_bound(qc+1,qc+cn+1,x)-qc;}
int m;
int S(int k,int N){
	N%=mod;
	if(k==1)return 1ll*N*(N+1)%mod*inv2%mod;
	return 1ll*N*(N+1)%mod*(2*N+1)%mod*inv6%mod;
}
int sol(int i,int j){
	int lp=max(s-r[j],l[i]),rp=min(r[i],s-l[j]);
	if(lp>rp)return 0;
	int B=(1ll*k[j]*s%mod+b[j])%mod;
	int sum=0;
	(sum+=1ll*b[i]*B%mod*(rp-lp+1)%mod)%=mod;
	(sum+=(-1ll*b[i]*k[j]%mod+1ll*k[i]*B%mod)*(S(1,rp)-S(1,lp-1))%mod)%=mod;
	(sum-=1ll*k[i]*k[j]%mod*(S(2,rp)-S(2,lp-1))%mod)%=mod;
	return sum;
}
int sol2(int i,int j){
	//s-2x>=l[j]
	if(s<l[j])return 0;
	int lp=max(((s<r[j])?0:(s-r[j]+1)/2),L[i]),rp=min((s-l[j])/2,R[i]);
	if(lp>rp)return 0;
	//k[j](s-2x)+b[j]
	//-2x*k[j]+b[j]+k[j]s
	int sum=0,B=(1ll*k[j]*s%mod+b[j])%mod;
	(sum+=1ll*B*(rp-lp+1)%mod)%=mod;
	(sum-=2ll*k[j]*(S(1,rp)-S(1,lp-1))%mod)%=mod;
	return sum;
}
int main(){
	cin>>n>>s;
	for(int i=1;i<=n;i++)scanf("%d%d",&L[i],&R[i]);
	int ans=0;
	if(!(s%4)){for(int i=1;i<=n;i++)if(L[i]<=s/4&&s/4<=R[i])ans-=6;}
	for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)if(s>=L[j]){
		//x:[L[i],R[i]]
		//3x:[s-R[j],s-L[j]]
		int rp=min(R[i],(s-L[j])/3),lp=max(L[i],((s<R[j])?0:(s-R[j]+2)/3));
		if(lp<=rp)(ans+=8ll*(rp-lp+1)%mod)%=mod;
	}
	if(!(s%2)){
		int t=s/2;
		for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)if(t>=L[j]){
			int rp=min(R[i],t-L[j]),lp=max(L[i],t-R[j]);
			if(lp<=rp)(ans+=3ll*(rp-lp+1)%mod)%=mod;
		}
	}
	for(int i=1;i<=n;i++)for(int j=1;j<=n;j++){
		int cs=L[i]+L[j],a=R[i]-L[i],b=R[j]-L[j];
		if(a>b)swap(a,b);
		qc[++cn]=cs,qc[++cn]=cs+a+1,qc[++cn]=cs+b+1,qc[++cn]=cs+a+b+1;
		//cs->1
	}
	sort(qc+1,qc+cn+1);cn=unique(qc+1,qc+cn+1)-qc-1;
	for(int i=1;i<=n;i++)for(int j=1;j<=n;j++){
		int cs=L[i]+L[j],c=R[i]-L[i],d=R[j]-L[j];
		if(c>d)swap(c,d);
		int A=F(cs),B=F(cs+c+1),C=F(cs+d+1),D=F(cs+c+d+1);
		//x:cs y:1
		k[A]++,k[B]--,(b[A]+=1-cs)%=mod,(b[B]-=1-cs)%=mod;
		(b[B]+=c+1)%=mod,(b[C]-=c+1)%=mod;
		//x:cs+a+b y:1 
		k[C]--,k[D]++;
		(b[C]+=cs+c+d+1)%=mod,(b[D]-=cs+c+d+1)%=mod;
	}
	for(int i=1;i<cn;i++){
		(b[i]+=b[i-1])%=mod,(k[i]+=k[i-1])%=mod,l[i]=qc[i],r[i]=qc[i+1]-1;
	}
	l[cn]=r[cn-1]+1;
	for(int i=1,j=cn,k=cn;i<cn;i++){
		while(j&&s-l[i]<l[j])j--;
		while(k&&s-r[i]<l[k])k--;
		for(int po=max(k,1);po<=min(j,cn-1);po++)(ans+=sol(i,po))%=mod;
	}
	for(int i=1,j=cn,k=cn;i<=n;i++){
		while(j&&s-2*L[i]<l[j])j--;
		while(k&&s-2*R[i]<l[k])k--;
		for(int po=max(k,1);po<=min(j,cn-1);po++)(ans-=6ll*sol2(i,po)%mod)%=mod;
	}
	int I=qpow(24,mod-2);
	return printf("%d",(1ll*ans*I%mod+mod)%mod),0;
}

六. AGC036D Negative Cycle

吹爆这个题。一年前就做(贺)过了,结果今天根本想不出来。

负环,考虑差分约束

那就是说我们现在有 \(x_0,x_1,…,x_{n-1}\)\(n\) 个变量,必须满足 \(x_{i+1}\le x_i\)

考虑确定 \(x\) 后哪些边能保留。

我们按照 \(x\) 的值把序列划分成若干段。

对于 \(i<j\) 的边,就是 \(x_j<x_i\) 能保留,那就等同于 \(i,j\) 不在同一段。

对于 \(j<i\) 的边,就是 \(x_j\le x_i+1\)

从此处就能看出,相邻两段的差一定要尽量小,也就是取成 \(1\)

此时 \(i,j\) 在同一段,或者 \(i,j\) 处在相邻两段。

显然就能设计 \(dp_{i,j}\) 表示当前段为 \([i,j]\) 的最小值,直接转移即可。

七. AGC050D Shopping

简单题。

我们要算什么?每个人能拿物品的概率,所以 dp 的状态值肯定是某种概率。

事实上,如果一个人领到了物品,我们直接当成把他删掉,显然不影响答案。

考虑没有领到的物品的人,令当前是第 \(x\) 轮,显然他试过 \(x-1\) 个物品,那么就是在 \(k-(x-1)\) 个物品里随机选。

我们令现在剩下 \(m\) 个人,那么就只有 \(k-m\) 个物品是未选的,当前这个人能领到物品的概率就是 \((k-m)/(k-x+1)\)

我们就针对一个人能选到的概率设计 \(dp\)

\(dp_{x,y,z,w}\) 表示"小明"左边有 \(x\) 个人,右边有 \(y\) 个人,然后游戏进行到了 \(x+y+1\) 个人中的第 \(z\) 个人,当前是第 \(w\) 轮,"小明"能拿到物品的概率。

转移简单。

八. AGC045D Lamps and Buttons

Snuke 肯定是从左往右依次撸,如果撸到自环就失败了,如果不是自环,显然所在的这个环可以确定并把灯全部点亮。

所以 Snuke 能 win 的条件是,令第一个自环的位置在 \(t\) ,则 \(A+1\)\(n\) 的任意点所在的环内都必须有一个数 \(<t\)

我们枚举 \(t\) ,为了保证他是第一个,需要容斥,就是钦定 \([1,t-1]\) 的一个集合都是自环。

然后现在问题就形如有一个长度为 \(a+b+c\) 的排列,\([a+1,a+b]\) 的所有数所在的环都存在 \([1,a]\) 的数,求方案。

我们考虑从 \(1\)\(a+b+c\) 顺次插入过程。

考虑这个过程等同于,每次插入 \(i\) ,你可以连 \((i,i)\) ,也可以选择已有的边 \((x,y)\) 把它断掉,连上 \((x,i)\)\((i,y)\)

于是插入 \(1\)\(a\) 是随意的,插入第 \(i\) 个时方案乘上 \(i\)\(a+1\)\(a+b\) 时,我们不能连自环,插入第 \(i\) 个时乘上 \(i-1\) 。最后 \(a+b+1\)\(a+b+c\) 也是任意的,插入第 \(i\) 个时方案乘上 \(i\)

答案就是 \(a(a+b+c)!/(a+b)\)

复杂度 \(O(A^2+n)\)

九. TopCoder 13369 TreeDistance / CF917D Stranger Trees

经典题?和 [WC2019] 数树类似。

题意:给你一棵 \(n\) 个点的树,求有多少棵 \(n\) 个点的树与它的边集交大小为 \(k\)

题解在这里

十. Comet OJ Contest #12 E Ternary String Counting

题目链接

我这个做法我感觉好想又方便!

考虑 \(f_{i,j,k}\) 表示考虑到第 \(k\) 个,当前三个颜色的末尾分别在 \(i,j,k\) 的方案数。\((i<j<k)\)

题目给出的限制显然能整理成:\(k\) 固定时,\(i,j\) 的上下界。

我们有转移:

考虑 \(k\) 从小往大枚举,于是我们可以不用记第三维。

\((i,j)\) 转移到 \((i,j)\)\((j,k)\)\((i,k)\) ,然后把不合法的清成 \(0\)

先不管 \(i,j\) 的大小限制,我们动态维护一个 \(dp_{i,j}\)

我们把这个 \(dp\) 看成一个矩阵,那么相当于我们每次都新添了一列,其中第 \(i\) 行的数等于原矩阵中第 \(i\) 列数与第 \(i\) 行数的和。

我们记 \(X_i\) 表示第 \(i\) 列数与第 \(i\) 行数的和并维护它,即可做到 \(O(n^2)\) 维护。

现在考虑 \(i,j\) 的大小限制,等同于需要把不在某矩阵中的所有格子给删掉。

我们把 \(k\)\(n\)\(1\) 枚举,记录 \(A_i,B_i\) 表示 \(i\) 行、\(i\) 列分别被清的最近时间。

那我们就能以此求出 \(T_{i,j}\) 表示每个格子被删的时间。

然后再来进行 \(dp\) 的计算。每次我们新添加一个格子 \((i,j)\),它相当于对 \(X_i,X_j\) 有贡献,但贡献会在某一刻消失。

这个可以差分维护。

于是复杂度做到了 \(O(n^2)\)

view code
#include<bits/stdc++.h>
using namespace std;
int TT,n,m,xl[5010],xr[5010],yl[5010],yr[5010];
int X[5010][5010];
struct ed{int l,r;};
const int mod=1e9+7;
int T[5010][5010],Kx[5010],Ky[5010];
void sol(){
	for(int i=1;i<=n;i++)if(xl[i]>xr[i]||yl[i]>yr[i]){
		puts("0");
		return;
	}
	for(int i=0;i<=n;i++)for(int j=0;j<=n;j++)X[i][j]=0;
	for(int i=0;i<=n;i++)Kx[i]=Ky[i]=n+1;
	for(int i=n;i;i--){
		for(int j=0;j<=n;j++)if(j<=xl[i]||j>xr[i]+1)Kx[j]=i;
		for(int j=0;j<=n;j++)if(j<=yl[i]||j>yr[i]+1)Ky[j]=i;
		for(int j=0;j<i;j++)T[j][i]=min(Kx[j],Ky[i]);
	}
	X[1][0]=X[1][1]=1;
	X[T[0][1]+1][0]--,X[T[0][1]+1][1]--;
	int ans=0;
	if(T[0][1]==n+1)ans++;
	for(int i=2;i<=n;i++){
		for(int j=0;j<=n;j++){
			(X[i][j]+=X[i-1][j])%=mod;
			if(j<i){
				int dk=X[i][j];
				(X[i][j]+=dk)%=mod;
				(X[i][i]+=dk)%=mod;
				(X[T[j][i]+1][j]-=dk)%=mod;
				(X[T[j][i]+1][i]-=dk)%=mod;
				if(T[j][i]==n+1)(ans+=dk)%=mod;
			}
		}
	}
	printf("%d\n",(3ll*ans%mod+mod)%mod);
}
int main(){
	scanf("%d",&TT);
	while(TT--){
		scanf("%d%d",&n,&m);
		for(int i=1;i<=n;i++)xl[i]=-1,xr[i]=i-2,yl[i]=0,yr[i]=i-1;
		for(int i=1,l,r,k;i<=m;i++){
			scanf("%d%d%d",&l,&r,&k);
			if(k==1)yr[r]=min(yr[r],l-1);
			if(k==2)yl[r]=max(yl[r],l),xr[r]=min(xr[r],l-1);
			if(k==3)xl[r]=max(xl[r],l);
		}
		sol();
	}
	return 0;
}

十一. ARC080F Prime Flip

CF79D Password,但是数据范围不一样。题解在这里

十二. AGC028D Chords

挺好的。考虑这个数联通块个数,显然我们考虑一个子集成为一个联通块的方案数。

但是这个很复杂。不妨想,我们用最左和最右的点代表这个子集。

也就是说,我们统计两个点 \(x,y\) 联通且是所在联通块最左最右点。

那么 \([x,y]\) 内的点显然不会往外连边。于是 \([x,y]\) 外的方案容易计算。

现在考虑 \(f_{x,y}\) 表示 \([x,y]\) 内连边使 \(x,y\) 联通的方案数。

这个区间 dp ,容斥一下就好了,具体是先把 \([x,y]\) 内总连边方案算出来,再减去 \(\sum f_{x,i}*g_{i+1,y}\) 即可。

复杂度 \(O(n^3)\)

十三. CF1149C Tree Generator™

会括号序求 \(LCA\) 这个题就没难度吧。

考虑树的括号序意味着,遇到 "(" 就是往某个儿子走,遇到 ")" 就是往父亲走。

一条路径 \((u,v)\) 就可以表示成你从 \(u\) ,通过一串括号,走到 \(lca\),此时路径长度是右括号数-左括号数;然后再走到 \(v\) ,长度是左括号数-右括号数。

把 "(" 看成 \(1\) ,")" 看成 \(-1\) ,问题就变成找两个相邻子段,使后一段的和-前一段的和最大。

这个线段树维护即可。

十四. XX Open Cup. Grand Prix of Zhejiang. F Fast as Ryser

UPD on 11.12:发现 PER #3 搬了这个题,https://pjudge.ac/contest/1008/problem/21682.

题意:给你一个无向图,一个匹配的权值是 \(c^{|S|}\)\(S\) 是你选的边集。

问你所有匹配的权值和。

\(N\le 36\)

对我而言的神题!

\(n=N/2\)

看到 \(N\le 36\) 这个条件,猜测是折半,把点分成两组什么的,但是发现没啥用。

换个想法:把点两两分组!

为了方便描述,考虑在 \(2i-1\)\(2i\) 之间建一条虚边。我们把 \(2i-1\)\(2i\) 构成的组叫组 \(i\)

再加上匹配边,图形成了若干个环和链。

\(ways(S)\) 表示集合 \(S\) 以内的组被串成一个联通块,所有方案的总权值。这个是可以经典 \(O(2^nn^2)\) 计算的。

考虑整个匹配的权值,就是很套路的 \(dp\) 了,\(dp(S)=ways(S)+ways(T)*dp(S-T)\) ,其中 \(T\)\(S\) 的真子集,并且包含 \(S\) 中最小组。

这个可以直接 \(O(3^n)\) ,也可以用一些我没学过的集合幂级数相关知识优化至 \(O(2^nn^2)\) ?

这里先留个坑。

坑已填。考虑这里是 \(dp=e^{ways}\) ,直接套板算即可。

($O(3^n)$做法)
#include<bits/stdc++.h>
using namespace std;
const int mod=1e9+7,inv2=(mod+1)/2;
int n,m,c,ci[40],pp[1<<18];
bool aj[36][36];
int dp[1<<18][36],h[1<<18];
int g[1<<18];
int main(){
	scanf("%d%d%d",&n,&m,&c);
	if(n&1)n++;n/=2;
	ci[0]=1;
	for(int i=1;i<=n;i++)ci[i]=1ll*ci[i-1]*c%mod;
	for(int i=0,u,v;i<m;i++){
		scanf("%d%d",&u,&v);
		u--,v--;
		aj[u][v]=aj[v][u]=1;
	}
	for(int s=1;s<(1<<n);s++)pp[s]=pp[s>>1]+(s&1);
	for(int i=0;i<n;i++)dp[1<<i][2*i]=dp[1<<i][2*i+1]=1;
	for(int s=1;s<(1<<n);s++)for(int i=0;i<n*2;i++)if(dp[s][i])
		for(int j=0;j<n*2;j++)if(!((s>>(j/2))&1)&&aj[i][j])	
			(dp[s|(1<<(j/2))][j^1]+=dp[s][i])%=mod;
	for(int s=1;s<(1<<n);s++)if((s&-s)!=s)for(int i=0;i<n*2;i++)if(dp[s][i])(h[s]+=1ll*dp[s][i]*ci[pp[s]-1]%mod*inv2%mod)%=mod;
	memset(dp,0,sizeof(dp));
	for(int i=0;i<n;i++)dp[1<<i][i*2]=1;
	for(int s=1;s<(1<<n);s++)for(int i=0;i<n*2;i++)if(dp[s][i])
		for(int j=0;j<n*2;j++)if(aj[i][j]&&(s&-s)<(1<<(j/2))&&!((s>>(j/2))&1))
			(dp[s|(1<<(j/2))][j^1]+=dp[s][i])%=mod;
	for(int s=1;s<(1<<n);s++)for(int i=0;i<n*2;i++)if(dp[s][i]){
		int ik=(int)log2(s&-s);
		if(aj[ik*2+1][i])(h[s]+=1ll*dp[s][i]*ci[pp[s]]%mod)%=mod;
	}
	int ans=1;
	for(int s=1;s<(1<<n);s++){
		g[s]=h[s];
		for(int s2=(s-1)&s;s2;s2=(s2-1)&s)
			if((s2&-s2)==(s&-s))	
				(g[s]+=1ll*h[s2]*g[s^s2]%mod)%=mod;
		(ans+=g[s])%=mod;
	}
	return printf("%d",(ans%mod+mod)%mod),0;
}
($O(2^nn^2)$做法)
#include<bits/stdc++.h>
using namespace std;
const int mod=1e9+7,inv2=(mod+1)/2;
int n,m,c,ci[50],pp[1<<20];
bool aj[50][50];
int dp[1<<20][42],h[1<<20];
void FMT(int *a,int f){
	for(int o=1;o<(1<<n);o<<=1)
		for(int i=0;i<(1<<n);i+=(o<<1))for(int j=0;j<o;j++)
			(a[i+j+o]+=a[i+j]*f)%=mod;
}
int F[22][1<<20],G[22][1<<20],inv[23];
int main(){
	scanf("%d%d%d",&n,&m,&c);
	if(n&1)n++;n/=2;
	ci[0]=inv[1]=1,ci[1]=c;
	for(int i=2;i<=n;i++)inv[i]=mod-1ll*inv[mod%i]*(mod/i)%mod,ci[i]=1ll*ci[i-1]*c%mod;
	for(int i=0,u,v;i<m;i++){
		scanf("%d%d",&u,&v);
		u--,v--;
		aj[u][v]=aj[v][u]=1;
	}
	for(int s=1;s<(1<<n);s++)pp[s]=pp[s>>1]+(s&1);
	for(int i=0;i<n;i++)dp[1<<i][2*i]=dp[1<<i][2*i+1]=1;
	for(int s=1;s<(1<<n);s++)for(int i=0;i<n*2;i++)if(dp[s][i])
		for(int j=0;j<n*2;j++)if(!((s>>(j/2))&1)&&aj[i][j])	
			(dp[s|(1<<(j/2))][j^1]+=dp[s][i])%=mod;
	for(int s=1;s<(1<<n);s++)if((s&-s)!=s)for(int i=0;i<n*2;i++)if(dp[s][i])(h[s]+=1ll*dp[s][i]*ci[pp[s]-1]%mod*inv2%mod)%=mod;
	memset(dp,0,sizeof(dp));
	for(int i=0;i<n;i++)dp[1<<i][i*2]=1;
	for(int s=1;s<(1<<n);s++)for(int i=0;i<n*2;i++)if(dp[s][i])
		for(int j=0;j<n*2;j++)if(aj[i][j]&&(s&-s)<(1<<(j/2))&&!((s>>(j/2))&1))
			(dp[s|(1<<(j/2))][j^1]+=dp[s][i])%=mod;
	for(int s=1;s<(1<<n);s++)for(int i=0;i<n*2;i++)if(dp[s][i]){
		int ik=(int)log2(s&-s);
		if(aj[ik*2+1][i])(h[s]+=1ll*dp[s][i]*ci[pp[s]]%mod)%=mod;
	}
	int ans=1;
	for(int s=1;s<(1<<n);s++)(F[pp[s]][s]+=h[s])%=mod;
	for(int i=1;i<=n;i++)FMT(F[i],1);
	for(int s=1;s<(1<<n);s++){
		G[0][s]=1;
		for(int i=1;i<=n;i++){
			G[i][s]=0;
			for(int j=1;j<=i;j++)(G[i][s]+=1ll*F[j][s]*j%mod*G[i-j][s]%mod)%=mod;
			G[i][s]=1ll*inv[i]*G[i][s]%mod;
		}
	}
	for(int i=1;i<=n;i++)FMT(G[i],-1);
	for(int s=1;s<(1<<n);s++)(ans+=G[pp[s]][s])%=mod;
	return printf("%d",(ans%mod+mod)%mod),0;
}
posted @ 2022-10-17 21:03  grass8woc  阅读(106)  评论(0编辑  收藏  举报