题目大意:平面直角坐标系上有2n个点,x轴上(1~n,0)有n个bot,y轴同理。启动x轴上的bot会向上走,y轴上的会向右走,走到第一个小球并和小球发生湮灭。问(2n)!种启动顺序中有多少方案能使小球全部湮灭。
题解:
考虑二分图,每个小球对应X连Y的一条边。
那么如果先不考虑顺序,考虑分配方案,相当于是每条边选择一个端点。
例如x-y这条边选择x作为端点,并且x是X中的点,那么意思是我选择用(x,0)的bot湮灭(x,y)的小球。
显然这样每个连通块必须是基环树,不同连通块基本无关。
然后树上的边选的端点是确定的:远离环的那个。
环上的只有两种情况,枚举一下即可。
然后考虑顺序问题,显然对于边x->y->z,如果x<z,那么x->y就要早于y->z执行。用边的起点表示边,就是x要早于y,或者说y晚于x。后者形成一个外向的森林,方案数是cnt!/(prod sz)。
最后用组合数插起来即可。
#include<bits/stdc++.h>
#define gc getchar()
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define Rep(i,v) rep(i,0,(int)v.size()-1)
#define lint long long
#define mod 1000000007
#define N 200010
#define db double
#define pb push_back
#define mp make_pair
#define fir first
#define sec second
#define debug(x) cerr<<#x<<"="<<x
#define sp <<" "
#define ln <<endl
using namespace std;
typedef pair<int,int> pii;
typedef set<int>::iterator sit;
inline int inn()
{
int x,ch;while((ch=gc)<'0'||ch>'9');
x=ch^'0';while((ch=gc)>='0'&&ch<='9')
x=(x<<1)+(x<<3)+(ch^'0');return x;
}
struct edges{
int to,pre;
}e[N<<1];int h[N],etop,onc[N],fa[N],vis[N],sz[N],d[N];
int fac[N],facinv[N],lst[N],ecnt,cnt,p[N],in[N];vector<int> g[N];
inline int fast_pow(int x,int k,int ans=1) { for(;k;k>>=1,x=(lint)x*x%mod) (k&1)?ans=(lint)ans*x%mod:0;return ans; }
inline int prelude(int n)
{
rep(i,fac[0]=1,n) fac[i]=(lint)fac[i-1]*i%mod;
facinv[n]=fast_pow(fac[n],mod-2);
for(int i=n-1;i>=0;i--) facinv[i]=facinv[i+1]*(i+1ll)%mod;
return 0;
}
inline int add_edge(int u,int v) { return e[++etop].to=v,e[etop].pre=h[u],h[u]=etop; }
int has_cyc=0;
int dfs(int x)
{
d[x]=d[fa[x]]+1,vis[x]=1,lst[++cnt]=x;int t=0;
for(int i=h[x],y;i;i=e[i].pre) if((y=e[i].to)^fa[x])
{ if(!vis[y]) fa[y]=x,dfs(y),ecnt++;else if(vis[y]&&d[y]<d[x]) t=y,ecnt++; }
if(t&&has_cyc) printf("0\n"),exit(0);
if(t) { while(x^t) onc[x]=1,x=fa[x];onc[t]=1,has_cyc=1; }
return 0;
}
int getclc(int x,int y,int z)
{
p[x]=y;if(y==z) return 0;
for(int i=h[y],w;i;i=e[i].pre)
if(onc[w=e[i].to]&&e[i].to!=x) getclc(y,w,z);
return 0;
}
int getsz(int x,int &ans)
{
sz[x]=1;Rep(i,g[x]) sz[x]+=getsz(g[x][i],ans);
return ans=(lint)ans*sz[x]%mod,sz[x];
}
inline int calc()
{
rep(t,1,cnt) g[lst[t]].clear(),in[lst[t]]=0;
rep(t,1,cnt)
{
int x=lst[t],y=p[x],z=p[y];
if(x<z) g[y].pb(x),in[x]++;
}
int ans=1,x;rep(t,1,cnt) if(!in[x=lst[t]]) getsz(x,ans);
return ans=(lint)fast_pow(ans,mod-2)*fac[cnt]%mod;
}
int getp(int x,int f=0)
{
for(int i=h[x],y;i;i=e[i].pre)
if((y=e[i].to)!=f&&!onc[e[i].to]) p[y]=x,getp(y,x);
return 0;
}
inline int solve(int x)
{
has_cyc=0,ecnt=0,cnt=0,fa[x]=0,dfs(x);
if(cnt^ecnt) printf("0\n"),exit(0);int ans=0;
rep(i,1,cnt) if(onc[x=lst[i]]) getp(x);
rep(t,1,cnt) if(onc[x=lst[t]])
{
for(int i=h[x],y;i;i=e[i].pre) if(onc[y=e[i].to])
getclc(x,y,x),ans+=calc(),(ans>=mod?ans-=mod:0);
break;
}
return ans;
}
int main()
{
int n=inn(),ans=1,x,y;prelude(n*2);
rep(i,1,2*n) x=inn(),y=inn()+n,add_edge(x,y),add_edge(y,x);
rep(i,1,2*n) if(!vis[i]) ans=(lint)ans*solve(i)%mod,ans=(lint)ans*facinv[cnt]%mod;
ans=(lint)ans*fac[2*n]%mod;return !printf("%d\n",ans);
}