【JZOJ5739】【20190706】毒奶

题目

\(n\)个现实城市,另有\(n\)个幻想城市

原图中在现实城市存在\(m\)条边,在幻想城市存在\(m-1-n\)条边

一个排列是合法的当且进当显示城市 \(i\) 向幻想城市 \(p_i\) 连边后,图是连通的

求合法的排列数目

\(n \le 20 \ , \ 时限10s\)

题解

  • 如果初始连通块个数\(\gt n+1\)个无解;
  • 考虑这\(n+1\)个连通块,设\(S\)表示已经分配好的连通块集合的合法排列个数
  • 转移时用总的减去不合法的,不合法考虑枚举编号最小的城市所在联通块
  • 时间复杂度:\(O(3^{20})\)
  • 由于一个状态如果不满足现实和幻想的连通块点数和相等就是无用的,可以去掉很多无用状态

Code

#include<bits/stdc++.h>
#define mod 998244353
using namespace std;
const int N=21;
int n,m,n1,n2,tot,f[N],sz[N],sz1[1<<N],sz2[1<<N],all1,all2,all,fac[N],F[1<<N],G[1<<N];
int find(int x){return f[x]==x?x:f[x]=find(f[x]);}
void dec(int&x,int y){x-=y;if(x<0)x+=mod;}
void uni(int u,int v){
	int fu=find(u),fv=find(v);
	if(fu==fv)return;
	f[fu]=fv;sz[fv]+=sz[fu];
}
int main(){
	freopen("milk.in","r",stdin);
	freopen("milk.out","w",stdout);
	
	scanf("%d%d",&n,&m);
	for(int i=fac[0]=1;i<=n;++i)fac[i]=1ll*fac[i-1]*i%mod;
	
	for(int i=1;i<=n;++i)f[i]=i,sz[i]=1;
	for(int i=1,u,v;i<=m;++i){
		scanf("%d%d",&u,&v);
		uni(u,v);
	}
	for(int i=1;i<=n;++i)if(find(i)==i){
		sz1[1<<tot++]=sz[i];
	}
	n1=tot;tot=0;all1=(1<<n1)-1;
	for(int i=0;i<n1;++i)
	for(int j=0;j<=all1;++j)
		if(j>>i&1)sz1[j]+=sz1[j^(1<<i)];
	
	m=n-1-m;
	for(int i=1;i<=n;++i)f[i]=i,sz[i]=1;
	for(int i=1,u,v;i<=m;++i){
		scanf("%d%d",&u,&v);
		uni(u,v);
	}
	for(int i=1;i<=n;++i)if(find(i)==i){
		sz2[1<<tot++]=sz[i];
	}
	n2=tot;tot=0;all2=(1<<n2)-1;
	for(int i=0;i<n2;++i)
	for(int j=0;j<=all2;++j)
		if(j>>i&1)sz2[j]+=sz2[j^(1<<i)];
	
	all=(1<<(n1+n2))-1;
	if(n1+n2!=n+1){puts("0");return 0;}
	F[0]=G[0]=1;
	for(int S=1;S<=all;++S){
		int v1=sz1[S&all1],v2=sz2[S>>n1];
		if(v1!=v2)continue;
		G[S]=fac[v1];
	}
	for(int S=1;S<=all;++S)if(G[S]){
		F[S]=G[S];
		int x=S&-S,R=S^x;
		if(x>all1)continue;
		for(int T=R;T;T=(T-1)&R)if(G[T]){
			dec(F[S],1ll*G[T]*F[S^T]%mod);
		}
	}
	cout<<F[all]<<endl;
	return 0;
}

posted @ 2019-07-07 16:41  大米饼  阅读(313)  评论(0编辑  收藏  举报