Arc083_F Collecting Balls
题目大意
给定$N$,在$(1,0),(2,0)......(N,0)$和$(0,1),(0,2)...(0,N)$上都有$1$个机器人,同时给定$2N$个坐标$(x,y),x,y\in[1,N]$上有障碍,你每次可以选择一个没有被选过的机器人$K$,若$X_K=0$,则它会沿着$y$轴正方向移动直到遇到遇到障碍或移出边界$[0,N]$,若它遇到障碍,则它会和障碍一同消失。
求选择$2N$个机器人的$(2N!)$中顺序中,有多少种会使得所有障碍消失。
题解
神仙题
考虑把每一行和每一列看做$2N$个点,对于障碍$(x,y)$,视为在$x$行$y$列之间连一条边。
显然,一条边只能被它的两个端点删掉,同一,一个点端点只能用于删掉一个条边。
那么对于每一个连通块,当且仅当它是一个基环树时才可能有解。
考虑树边,显然叶子节点只能删掉连接它的边,那么同理可得树边只能被距离环更远的端点删掉。
而环上的边也只有两种情况,每个点要么顺时针删去下一条边,要么逆时针删去下一条边,枚举即可。
假设我们已经确定了每个点删掉了哪条边。
假设我们用$x$点删掉了$(x,y)$,那么考虑$y$是$(x,y)$的另一维坐标,那么对于$x$连接的$k$,若$k<y$,删除$(x,y)$之前一定要先删除$(x,k)$,所以$k$一定要在$x$之前先选,可以再建一个图,新图中$k$向$x$连边。
于是我们就得到了一个有先后顺序要求的拓扑图。显然这是一个森林,因为对于任意点对$(a,b)$,一定不会存在两种方式从$a$到达$b$,而且新图上的边在原图中一定是从树枝的叶子一步步连向环的某一个节点,所以森林是若干颗内向树构成的。
考虑有$m$个点的内向树有多少种拓扑序。共有$m!$中排序方式,其中对于以$x$为根的子树,它可能是拓扑序当且仅当它是这$Size_x$个节点中($Size_x$表示其子树大小)的最后一个(在外向树中应该是第一个,所以有向树在去掉方向后同构则拓扑序数量),那么它是有效的序列只有$\frac{1}{Size_x}$种,每一棵子树可以独立考虑,所以拓扑序方案数就是$\frac{m!}{\prod\limits_{x=1}^{m} Size_x}$。
对于一棵内向树森林,则$Size$定义不变,$m$变为森林中所有点的点数。
由于环上的选边方式有两种,就枚举方向再加起来,就得到了消完这个连通块的方案数。
对于连通块$i$,设其点数为$m_i$,方案数为$Ans_i$,一共有$K$个连通块。
最后的总方案数是$$\prod\limits_{i=1}^{K}\dbinom{\sum\limits_{j=i}^{K}m_j}{m_i}\times Ans_i$$
虽然已经可以算了,但是我们还可以通过把$\dbinom{n}{m}$化为$\frac{n!}{m!(n-m)!}$的形式,不难发现这个上式就变成了$$\frac{(2N)!}{\prod\limits_{i=1}^{K}m_i}\prod\limits_{i=1}^{K}Ans_i$$更加直观且容易计算。
复杂度$O(n)$。
#include<bits/stdc++.h> #define LL long long #define M 200020 #define mod 1000000007 using namespace std; namespace IO{ const int BS=(1<<22)+5; int Top=0; char Buffer[BS],OT[BS],*OS=OT,*HD,*TL,SS[20]; const char *fin=OT+BS-1; inline char Getchar(){if(HD==TL){TL=(HD=Buffer)+fread(Buffer,1,BS,stdin);} return (HD==TL)?EOF:*HD++;} inline void write(int x){ if(!x){putchar('0');return;} if(x<0) x=-x,putchar('-'); while(x) SS[++Top]=x%10,x/=10; while(Top) putchar(SS[Top]+'0'),--Top; } inline int read(){ int nm=0; char cw=Getchar(); for(;!isdigit(cw);cw=Getchar()); for(;isdigit(cw);cw=Getchar()) nm=nm*10+(cw-'0'); return nm; } } using namespace IO; #define mul(x,y) (LL)(x)*(y)%mod #define upd(x,y) x=((x)+(y)>=mod)?(x)+(y)-mod:(x)+(y) int n,m,fs[M],nt[M<<1],to[M<<1],tmp,ans=1,EG,top,ind[M],node[M]; int S[M],c[M],fa[M],nxt[M],fst[M],tar[M],cur,cnt,sz[M],inv[M],vis[M]; inline void link(int x,int y){ nt[tmp]=fs[x],fs[x]=tmp,to[tmp++]=y; nt[tmp]=fs[y],fs[y]=tmp,to[tmp++]=x; } inline void edge(int x,int y){nxt[cur]=fst[x],fst[x]=cur,tar[cur++]=y;} void init(int x,int last){ node[++cnt]=x,S[++top]=x,ind[x]=1,S[top+1]=0; for(register int i=fs[x];~i;i=nt[i],EG++){ if(to[i]==last) continue; if(!ind[to[i]]){init(to[i],x);continue;} if(m) continue; while(S[top+1]!=to[i]) vis[c[++m]=S[top--]]=true; c[0]=to[i]; } top--,ind[x]=2; } void build(int x,int last){ for(register int i=fs[x];~i;i=nt[i]){ if(vis[to[i]]||to[i]==last) continue; fa[to[i]]=x,build(to[i],x); } } int dfs(int x){ if(sz[x]) return 1; int res=sz[x]=1; for(register int i=fst[x];i!=-1;i=nxt[i]){ res=mul(res,dfs(tar[i])),sz[x]+=sz[tar[i]]; } return mul(res,inv[sz[x]]); } inline int calc(){ for(int i=1;i<=cnt;i++) fst[node[i]]=-1,sz[node[i]]=0; cur=0; int res=1; for(int x=1;x<=cnt;x++) for(register int i=fs[node[x]];i!=-1;i=nt[i]) if(to[i]<fa[node[x]]) edge(node[x],to[i]); for(int x=1;x<=cnt;x++) if(!sz[node[x]]) res=mul(res,dfs(node[x])); return res; } inline int solve(int x){ top=0,init(x,0); if(EG!=(cnt<<1)) puts("0"),exit(0); for(int i=1;i<=m;i++) build(c[i],0); int res=0; for(int i=1;i<=m;i++) fa[c[i-1]]=c[i]; upd(res,calc()); for(int i=1;i<=m;i++) fa[c[i]]=c[i-1]; upd(res,calc()); return res; } int main(){ n=read(),ans=inv[1]=1,memset(fs,-1,sizeof(fs)); for(int i=2;i<=(n<<1);i++) ans=mul(ans,i),inv[i]=mul(inv[mod%i],mod-(mod/i)); for(int i=1;i<=(n<<1);i++) link(read()+n,read()); for(int i=1;i<=(n<<1);i++) if(!ind[i]) cnt=m=EG=0,ans=mul(ans,solve(i)); printf("%d\n",ans); return 0; }