题意:
题目描述:
(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
",ans);
return 0;
}