[ARC083F] Collecting Balls [题解]

Collecting Balls

题面

在一个 \(O−xy\) 平面上有 \(2N\) 个小球,第 \(i\) 个球的坐标为 \((xi,yi)\) ,其中 \(1\leq xi,yi\leq N\) ,保证没有两个球在同一个点。

为了收集这些小球,我们准备了 \(2N\) 个机器人。其中有 \(N\) 个机器人在 \(x\) 轴上,有 \(N\) 个机器人在 \(y\) 轴上。具体来说这些机器人所在位置的坐标分别是 \((1,0),(2,0),…,(N,0)与(0,1),(0,2),…,(0,N)\)

  • 坐标为 \((a,0)\) 的机器人只会沿着 \(x=a\) 的正方向移动去收集小球,当他遇到一个小球的时候,这个机器人和小球就会同时消失,如果这个机器人不可能遇到一个小球,这个机器人也会消失
  • 坐标为 \((0,a)\) 的机器人只会沿着 \(y=a\) 的正方向移动去收集小球,当他遇到一个小球的时候,这个机器人和小球就会同时消失,如果这个机器人不可能遇到一个小球,这个机器人也会消失

现在这些机器人会依次出场去收集小球,也就是说只有当上一个机器人消失后下一个机器人才会出场,请问在所有 \((2N)!\) 种出场顺序中,这些机器人有多少种出场顺序使得将场上的球都收集走。答案很大,输出对 \(10^9+7\) 取模的值。

分析

首先,我们会发现,一个球最多有可能被两个机器人收集,考虑一种建图方式,将能够收集到同一个小球的机器人连接起来。

发现我们一共有 \(2n\) 个这样的点以及 \(2n\) 个这样的边。考虑我们在什么情况下是无解的,对于一个连通块,如果出现边的数量不等于点的数量,那我们就不能够收集完所有的小球,由于我们成功的方案需要一个点对应一条边,故不难想象此为无解。

那么,我们排除掉无解的情况后,得到的图中每个连通子图边和点数量都相等,即相当于我们得到了一个基环树森林,考虑得到这个森林后我们如何解答。

首先,对于每棵基环树,不在环上的点我们都能够找到唯一的边与其对应,在环上的点只存在两种对应情况,枚举这两种情况,即可确定每个小球所对应的机器人的编号。

之后,我们将每个机器人向必定比其先收集小球的机器人连边,这样我们能够得到一个森林,森林里的每棵树上的节点都在基环树上,同时子节点的顺序必须在父节点前,那我们如何统计每棵子树的答案呢,设 \(f[i]\) 表示选完子树 \(i\) 的方案数,那我们根据多重全排列可得:

\[f[i]=\frac{(size[i]-1)!}{\prod _{s\in son[i] }(size[s]!) } \prod_{s\in son[i]} f[s] \]

多个树的合并与之同理,之后我们能够得到每棵基环树的答案,最后再进行一遍类似的合并即可。

CODE

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 3e5 + 10, MOD = 1e9 + 7;
inline int read()
{
	int s = 0,w = 1;
	char ch = getchar();
	while(ch < '0' || ch > '9') { if( ch == '-') w *= -1; ch = getchar(); }
	while(ch >= '0' && ch <= '9') s = s * 10 + ch - '0',ch = getchar();
	return s * w;
}
struct node{ int x,y; }ball[2*N];
struct group{ int p,e; }cnt;
int n, s, ans, fac[2*N], f[2*N], siz[2*N];
int tot, v[4*N], id[4*N], nex[4*N], first[2*N];
int top, st[2*N], vis[2*N], father[2*N];
int ed, c[2*N], who[2*N], b[2*N], in[2*N];
bool Judge[2*N], point[2*N];
vector<int> vec[N],ver[2*N];
inline int power(int x, int k)
{
	int res = 1;
	while(k){
		if(k&1) res = res * x % MOD;
		k >>= 1, x = x * x % MOD; 
	}
	return res;
}
inline void Add(int x, int y, int z)
{
	nex[++tot] = first[x];
	first[x] = tot, v[tot] = y;
	id[tot]= z; //记录一下每条边对应的球的编号 
}
inline void check(int u)
{
	cnt.p++, vis[u] = 1, st[++top] = u;
	for(register int i = first[u] ; i ; i = nex[i]){
		if(point[i]) continue;
		int another = i&1 ? i+1 : i-1;
		point[i] = point[another] =true;
		b[++cnt.e] = id[i];
	}
	for(register int i = first[u] ; i ; i = nex[i]){
		int to = v[i];
		if(vis[to]) continue;
		check(to);
	}
}
inline void find(int u, int fa) //找环 
{
	father[u] = fa, vis[u] = 1;
	for(register int i = first[u]; i ;i = nex[i]){
		int to = v[i];
		if(to == fa) continue; 
		if(Judge[to]) continue;
		if(vis[to] && Judge[to]) continue;
		if(vis[to] && !Judge[to]){ //回归了环上的某个点 
			int x = u;
			while(x != to) c[++ed] = x, Judge[x] = true, x = father[x];
			c[++ed] = to, Judge[to] = true;
		}
		else find(to, u);
	}
}
inline void dfs(int u, int fa)
{
	for(register int i = first[u]; i ; i = nex[i]){
		int to = v[i];
		if(to == fa) continue;
		if(Judge[to]) continue; //不能再去环上的点 
		who[id[i]] = to, dfs(to, u);
	}
}
inline void dfs2(int u, int fa) //走环上的边 
{
	for(register int i = first[u]; i ; i = nex[i]){
		int to = v[i];
		if(to == fa) continue;
		if(!Judge[to]) continue;
		who[id[i]] = u;
		if(to != c[1]) dfs2(to, u);
	}
}

inline void dfs3(int u, int fa)
{
	siz[u] = 1, s++;
	for(register int i = 0; i < ver[u].size(); i++){
		int to = ver[u][i];
		if(to == fa) continue;
		dfs3(to, u);
		siz[u] += siz[to];
	}
	f[u] = fac[siz[u]-1];
	for(register int i = 0; i < ver[u].size(); i++){
		int to = ver[u][i];
		if(to == fa) continue;
		f[u] = f[u] * power(fac[siz[to]], MOD - 2) % MOD * f[to] % MOD;
	}
}
inline bool cmp1(int x, int y) { return ball[x].x < ball[y].x; }
inline bool cmp2(int x, int y) { return ball[x].y < ball[y].y; }
signed main()
{
	n = read();
	fac[0] = 1;
	for(register int i = 1; i<= 2 * n; i++) fac[i] = fac[i - 1] * i %MOD;
	for(register int i = 1; i <= 2 * n; i++){
		ball[i].x = read(), ball[i].y = read();
		Add(ball[i].x, ball[i].y + n, i), Add(ball[i].y + n, ball[i].x, i);
	}
	ans = fac[2 * n];
	for(register int i = 1; i <= 2 * n; i++){
		if(!vis[i]){
			cnt = (group){0, 0}, top=0, check(i);
			if(cnt.e != cnt.p) { puts("0"); return 0; }
			for(register int i = 1; i <= top; i++) vis[st[i]] = 0;
			ed=0, find(i, 0);
			//对于环,分两种情况讨论
			for(register int j = 1; j <= ed; j++)
				for(register int k = first[c[j]]; k ; k=nex[k]) dfs(c[j], 0); //从环上的点出发 
			sort(st + 1, st + top + 1); 
			int res = 0;
			for(register int j = first[c[1]]; j ; j = nex[j]){ //枚举环上的情况 
				int to = v[j];
				if(!Judge[to]) continue;
				who[id[j]] = c[1], dfs2(to, c[1]);
				for(register int k = 1; k <= cnt.e; k++)
					vec[ball[b[k]].x].push_back(b[k]); //先记录每一行的小球数量
				int mid=1;
				for(register int k = 1; k <= top; k++){ //枚举行数 
					if(st[k]<=n){ //行 
						sort(vec[st[k]].begin(),vec[st[k]].end(), cmp2);
						for(register int x = vec[st[k]].size() - 1; x >= 0; x--){ //枚举一下小球 
							if(who[vec[st[k]][x]]>n) continue; //不受行的小球的影响
							for(register int y = x - 1; y >= 0; y--){ //连边 
								ver[who[vec[st[k]][x]]].push_back(who[vec[st[k]][y]]);
								in[who[vec[st[k]][y]]]++;
							}
						}
						vec[st[k]].clear(), mid = k + 1;
					}	
				}
				for(register int k = 1; k <= cnt.e; k++)
					vec[ball[b[k]].y].push_back(b[k]);
				for(register int k = mid; k <= top; k++){ //枚举存在的列数 
					sort(vec[st[k]-n].begin(), vec[st[k]-n].end(), cmp1);
					for(register int x = vec[st[k]-n].size() - 1; x >= 0; x--){
						if(who[vec[st[k]-n][x]] <= n) continue; //不受列的小球的影响
						for(register int y = x - 1; y >= 0; y--){
							ver[who[vec[st[k]-n][x]]].push_back(who[vec[st[k]-n][y]]);
							in[who[vec[st[k]-n][y]]]++;
						} 
					}
					vec[st[k] - n].clear();
				}
				int sum = fac[top];
				for(register int k = 1; k <= top; k++){ //枚举记录的点 
					if(!in[st[k]]){
						s = 0, dfs3(st[k], 0);
						sum = sum * power(fac[s], MOD - 2) % MOD * f[st[k]] % MOD;
					}
				}
				for(register int k = 1; k <= top; k++) ver[st[k]].clear(), in[st[k]]=0;
				res = (res + sum) % MOD;
			}
			ans = ans * power(fac[top], MOD - 2) % MOD * res % MOD;
			
		}
	}
	printf("%lld\n", ans);
	return 0;
}



posted @ 2021-11-08 21:38  ╰⋛⋋⊱๑落叶๑⊰⋌⋚╯  阅读(68)  评论(2编辑  收藏  举报