题解:P11380 [GESP202412 八级] 排队

题目

小杨所在班级共有 \(n\) 位同学,依次以 \(1,2,\dots,n\) 标号。这 \(n\) 位同学想排成一行队伍,其中有些同学之间关系非常好,在队伍里需要排在相邻的位置。具体来说,有 \(m\) 对这样的关系(\(m\) 是一个非负整数)。当 \(m\geq 1\) 时,第 \(i\) 对关系(\(1\leq i\leq m\))给出 \(a_i,b_i\),表示排队时编号为 \(a_i\) 的同学需要排在编号为 \(b_i\) 的同学前面,并且两人在队伍中相邻。

现在小杨想知道总共有多少种排队方式。由于答案可能很大,你只需要求出答案对 \(10^9+7\) 取模的结果。

对于所有测试数据点,保证 \(1\leq n\leq 2\times 10^5\)\(0\leq m\leq 2\times 10^5\)

题解

现实中却是为了同学关系而把队列变成树,甚至完全图也有可能……

读题之后发现有两个点:

是否有解?

本人喜欢 Tarjan 和 \(O(\alpha(N))\) 数据结构,选用并查集喜欢关系。
本人喜欢 线性数据结构,选用链表维护相邻关系;

以下情况是不合法的:

  • \(a_i, b_i\) 在一个连通块内,说明出现了环;(并查集)
  • \(b_i\) 已经前驱了,否则会一对多;(链表)
  • \(a_i\) 已经后继了,否则会一对多;(链表)

注意判掉重边,重边是合法的。

解是多少?

对于每个连通块内其实是确定的,所以就是 \(A_{cnt}^{cnt}\)

#include<bits/stdc++.h>
#define LL long long

using namespace std;

const int MAXN = 200005;
const LL mod = 1e9+7;

int n, m;
LL res = 1;

int pre[MAXN];
int suf[MAXN];

int ancCnt;
int anc[MAXN];
inline void ancInit(int siz) {
	for (int i = 1; i <= siz; ++i)
		anc[i] = i;
	return;
}
inline int findAnc(int cur) {
	if (anc[cur] != cur)
		anc[cur] = findAnc(anc[cur]);
	return anc[cur];
}
inline void merge(int a, int b) {
	anc[findAnc(b)] = findAnc(a);
	return;
}

int main() {
	scanf("%d%d", &n, &m);
	ancInit(n);
	for (int i = 1; i <= m; ++i) {
		int a, b;
		scanf("%d%d", &a, &b);
		if (pre[b] == a && suf[a] == b)
			continue;
		int ancA = findAnc(a);
		int ancB = findAnc(b);
		if (ancA != ancB && !pre[b] && !suf[a]) {
			merge(a, b);
			suf[a] = b;
			pre[b] = a;
		} else
			cout << "0", exit(0);
	}
	for (int i = 1; i <= n; ++i)
		if (findAnc(i) == i)
			ancCnt++;
	for (int i = 1; i <= ancCnt; ++i)
		res = (res * i) % mod;
	cout << res;
	return 0;
}
posted @ 2025-08-12 16:31  Firsry  阅读(21)  评论(0)    收藏  举报