同桌与室友 计数类问题

题意:

题目描述:
\(X\)班有 N 个人,从\(1\)\(N\)编号。他们中有一些人住双人宿舍,一些人住单间,也
就是说一些人有唯一的一个室友,有些人则没有。同时有些人会和他的同桌共用一张双
人桌,另一些人则单独坐。
你需要求出有多少个排列\(P\),满足原本的人\(i\)换到\(P_i\)的宿舍以及桌子上后,原本
的室友以及同桌关系依旧不变,答案对\(10^9 + 7\)取模。
输入格式
第一行三个整数\(N,M1,M2\),表示人数,双人宿舍数量,双人桌数量。
接下来\(M1\)行,每行两个整数\(x,y\),表示\(x\)\(y\)同住一间双人宿舍。
接下来\(M2\)行,每行两个整数\(x,y\),表示\(x\)\(y\)同用一张双人桌。
输出格式
输出一行一个整数,表示满足条件的排列数量。
样例 1 输入
7 2 2
1 2
3 4
1 4
5 6
样例 1 输出
4
样例 2 输入
5 2 1
1 2
3 4
1 4
2
样例 2 输出
2
数据范围与约定
对于\(5\)%的数据,\(1 ≤ N ≤ 1\);
对于 \(20\)% 的数据,\(1 ≤ N ≤ 10\);
对于额外\(15\)% 的数据,一个人要么住单人间,要么就用单人桌;
对于额外 \(15\)% 的数据, 保证 \(N\) 是偶数,\(M1 = M2 =N/2\)
对于额外 \(20\)% 的数据, 保证数据完全随机生成;
对于\(100\)% 的数据,保证 \(1 ≤ N ≤ 2 × 10^5,M1, M2 ≤N/2\)

一句话版:一张图有黑白两种边,求使得图同构的置换数

思路:

将双人宿舍关系看做是黑边,双人桌关系看做是白边,那么就是一个图同构的计数
考试时一看是图同构,打了个暴力就跑
其实这道题的图有特殊之处
每个点度数最多为2(白边黑边各一条)
因此每一个连通块要么是一个简单环,要么是一条链
分类计数即可。
具体来说,
如果有 \(x\) 个大小为 \(i\) 的环,那么这一部分答案为 \(x! × i^x\)
如果有 \(x\) 个长度相同,两段边同为黑或同为白的链,那么这一部分答案为 \(x! × 2^x\)
如果有 \(x\) 个长度相同,两段边不同色的链,那么这一部分答案为 \(x!\)

注意事项:

做一道题最好能把题意极为简洁地概括出来,方便分析
牢牢抓住一道题的特殊点

code:

#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5;
const int p=1e9+7;
int n,m1,m2,rg,cnt,cl,ans=1;
int to[N][2],r[N],l[N][2];
bool vis[N];
inline int read()
{
	int s=0,w=1; char ch=getchar();
	for(;!isdigit(ch);ch=getchar())if(ch=='-')w=-1;
	for(;isdigit(ch);ch=getchar())s=(s<<1)+(s<<3)+(ch^48);
	return s*w;
}
void dfs(int u,int tp)
{
	vis[u]=true;++cnt;
	if(tp==-1)
	{
		if(to[u][0]&&!vis[to[u][0]]) dfs(to[u][0],0);
		if(to[u][1]&&!vis[to[u][1]]) dfs(to[u][1],1);
		return;
	}
	if(!to[u][tp^1]){rg=1;cl=tp;return;}
	if(vis[to[u][tp^1]]) return;
	dfs(to[u][tp^1],tp^1);
}
int main()
{
	n=read(),m1=read(),m2=read();
	for(int i=1;i<=m1;++i)
	{
		int x=read(),y=read();
		to[x][0]=y,to[y][0]=x;
	}
	for(int i=1;i<=m2;++i)
	{
		int x=read(),y=read();
		to[x][1]=y,to[y][1]=x;
	}
	for(int i=1;i<=n;++i)
	{
		if(vis[i]) continue;
		rg=0;cnt=0;dfs(i,-1);
		if(!rg)
		{
			ans=1ll*ans*cnt%p;
			++r[cnt];
			ans=1ll*ans*r[cnt]%p;
		}
		else
		{
			if(cnt&1)
			{
				++l[cnt][0];
				ans=1ll*ans*l[cnt][0]%p;
			}
			else
			{
				ans=ans*2ll%p;
				++l[cnt][cl];
				ans=1ll*ans*l[cnt][cl]%p;
			}
		}
	}
	printf("%d\n",ans);
	return 0;
}
posted @ 2020-01-20 16:22  BILL666  阅读(409)  评论(0编辑  收藏  举报