AT3537 [ARC083D]Collecting Balls 题解
Post time: 2021-09-22 21:55:13
一道图论(树论)综合好题,被亓神扒出来放到了 vc 胡策上。
首先发现球的数量等于机器人的数量,也就是说,每一个机器人都必须要吃掉一个球。感觉上直接做不好做,考虑转化,把每个球当做边,将横纵坐标上的机器人连起来。
连完之后,发现形成了一个边数等于点数的图。那么上边第一个限制,相当于是给每一条边定向,最后每一条边负责一个点。也就是说,这个边数等于点数的图想要满足这个条件必须形成一个基环树森林。
考虑森林里的每一棵基环树,有一个条件是,假设 \((x_0,0)\) 的机器人吃掉了 \((x_0,y_0)\) 的球,那么不管怎么样,所有 \((x_0,y')(y'<y_0)\) 的球都必须被先吃掉。感觉上这个东西连出来的图是一个 DAG,但是实际上它比 DAG 还要更优,由于每一个点只会有至多一个父亲,所以会形成内向树森林。现在就只需要对内向树森林的拓扑序计数了,这个东西可以用一个式子简单实现:
\[total=\frac{(|S|)!}{\sum_{u\in S}siz_u}
\]
其中 \(S\) 是树的点集。
对于每一棵基环树,由于其存在环,环定向有两个方向,所以需要求两次,方案数直接相加即可。最后对所有基环树乘一个组合数就完事了。
点击查看代码
#include<iostream>
#include<cstdio>
#include<vector>
#define pb push_back
using namespace std;
typedef long long ll;
const int N=2e5+13,mod=1e9+7;
inline int qpow(int a,int k){int s=1;for(;k;k>>=1,a=(ll)a*a%mod)if(k&1)s=(ll)s*a%mod;return s;}
int n,mul[N],invmul[N],inv[N];
inline void init(){
mul[0]=invmul[0]=1;
for(int i=1;i<=n;++i) mul[i]=(ll)mul[i-1]*i%mod;
invmul[n]=qpow(mul[n],mod-2);
for(int i=n-1;i;--i) invmul[i]=(ll)invmul[i+1]*(i+1)%mod;
for(int i=1;i<=n;++i) inv[i]=qpow(i,mod-2);
}
struct Edge{int v,nxt;}e[N<<1],E[N<<1];
int h[N],tot,cnt1,cnt2,bgn1,bgn2,f[N],ind[N],siz[N];
vector<int> d,g[N];
bool vis[N];
inline void add_edge(int u,int v){e[++tot]=(Edge){v,h[u]};h[u]=tot;}
inline void Add_edge(int u,int v){g[u].pb(v);}
inline void clear(){d.clear();cnt1=cnt2=0;}
void dfs1(int u,int fa){
vis[u]=1,d.pb(u);++cnt1;
for(int i=h[u];i;i=e[i].nxt){
++cnt2;
int v=e[i].v;if(v==fa) continue;
if(!vis[v]) dfs1(v,u);
else bgn1=u,bgn2=v;
}
}
void dfs2(int u,int fa,int stop){
f[u]=fa;
for(int i=h[u];i;i=e[i].nxt){
int v=e[i].v;if(v==fa||v==stop) continue;
dfs2(v,u,stop);
}
}
void dfs3(int u,int fa){
siz[u]=1;
for(auto v:g[u])dfs3(v,u),siz[u]+=siz[v];
}
inline int wk(){
dfs2(bgn1,bgn2,bgn1);
for(auto u:d) ind[u]=0,g[u].clear();
for(auto u:d){
for(int i=h[u];i;i=e[i].nxt){
int v=e[i].v;
if(v<f[u]) Add_edge(u,v),++ind[v];
}
}
for(auto u:d)if(!ind[u]) dfs3(u,u);
int ans1=mul[cnt1];
for(auto u:d) ans1=(ll)ans1*inv[siz[u]]%mod;
dfs2(bgn2,bgn1,bgn2);
for(auto u:d) ind[u]=0,g[u].clear();
for(auto u:d){
for(int i=h[u];i;i=e[i].nxt){
int v=e[i].v;
if(v<f[u]) Add_edge(u,v),++ind[v];
}
}
for(auto u:d)if(!ind[u]) dfs3(u,u);
int ans2=mul[cnt1];
for(auto u:d) ans2=(ll)ans2*inv[siz[u]]%mod;
return (ans1+ans2)%mod;
}
int main(){
scanf("%d",&n);
for(int i=1;i<=(n<<1);++i){
int u,v;scanf("%d%d",&u,&v);
add_edge(u,v+n),add_edge(v+n,u);//对于每个球,给相应的机器人连无向边连成基环树森林
}
n<<=1;init();
int ans=mul[n];
for(int i=1;i<=n;++i){
if(vis[i]) continue;
clear();dfs1(i,i);//dfs1用来找环
if(2*cnt1!=cnt2) return puts("0"),0;//如果不是基环树,就无解
ans=(ll)ans*invmul[cnt1]%mod;//这个和上边赋初值都是用来算组合数的
int res=wk();//环的两种方向一起计算
ans=(ll)ans*res%mod;
}
printf("%d\n",ans);
return 0;
}