题目大意:一个经典的游戏:抢椅子。有\(n\)个人以及\(2n\)把椅子。开始时每个人坐在一把椅子上,而且他们每个人都有一个下一步想坐的位置(可以与之前重合)。每一个下一次可以在自己现在做的椅子和想坐的椅子上选择一个坐下。任意两个人不得坐在同一张椅子上。求合法的方案数。
手动翻译真是累,图论题一言不合先建图,讲每个人现在坐的椅子和想要做的椅子连边(这样就有了自环)
一眼想到二分图,我是ZZ
我们考虑这个图的结构,可以分成多个联通块考虑。对于每一个联通块,我们都进行讨论(设联通块大小为\(x\)):
- 树状,这个时候很简单,相当于\(x-1\)个人(因为只有\(x-1\)条边)抢\(x\)把椅子。因此每次只有一把椅子空出,方案数即为\(x\)
- 环状(包括环套树),此时一个环上的方案数只有两种,即要么都不动,要么一起动。然后当环上的点都确定了之后其它的情况就唯一确定了(请自行画图理解)
- 自环,这个要特别讨论,此时方案数很显然就是\(1\)
然后我们根据乘法原理直接乘起来就好了。
不过主要这里我们只需要判断一个联通块是否成环以及计算环的大小即可
这个在并查集的时候顺带维护一下个数即可。连图都不用建
CODE
#include<cstdio>
#include<cctype>
using namespace std;
const int N=100005,mod=1e9+7;
int father[N<<1],size[N<<1],n,x,y,ans=1;
bool cir[N<<1],loop[N<<1];
inline char tc(void)
{
static char fl[100000],*A=fl,*B=fl;
return A==B&&(B=(A=fl)+fread(fl,1,100000,stdin),A==B)?EOF:*A++;
}
inline void read(int &x)
{
x=0; char ch; while (!isdigit(ch=tc()));
while (x=(x<<3)+(x<<1)+ch-'0',isdigit(ch=tc()));
}
inline int getfather(int k)
{
return father[k]^k?father[k]=getfather(father[k]):k;
}
int main()
{
//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
register int i; read(n);
for (i=1;i<=n<<1;++i)
father[i]=i,size[i]=1;
for (i=1;i<=n;++i)
{
read(x); read(y);
if (x==y) loop[x]=1;
int fx=getfather(x),fy=getfather(y);
if (fx!=fy)
{
father[fx]=fy; loop[fy]|=loop[fx];
size[fy]+=size[fx]; size[fx]=0;
} else cir[fx]=1;
}
for (i=1;i<=n<<1;++i)
if (getfather(i)==i&&!loop[i])
{
if (cir[i]) ans=ans*2%mod; else ans=1LL*ans*size[i]%mod;
}
return printf("%d",ans),0;
}