1. 程式人生 > 其它 >H Permutation Counting(並查集 + dfs)

H Permutation Counting(並查集 + dfs)

H Permutation Counting

https://ac.nowcoder.com/acm/contest/34866/H

題意

給定n以及m對限制條件 x y代表Px 要比 Py小
求符合限制條件的1-n的全排列有多少

思路

用並查集 維護相互制約的幾個數 不同集合之間就是獨立的
那麼就可以根據每個集合的大小 用組合數 計算出n個數分成那幾個集合的方案數 \(C_{sum}^size\)
每次確定一個集合剩餘數的總數(sum)就要減去這個集合大小
然後根據集合中的制約關係在增加方案數
對於一個集合可以看成一顆單獨的樹 根節點是最先的祖先
每次對於一個節點我們都能確定最大的那個是那個數 然後它的延伸的子樹可以隨機分配
要實現這個我們必須知道以每個節點為根節的子樹的大小 (用深搜遞迴從深往淺傳遞樹的大小)
這樣才能用$C_{單獨兒子子樹的大小}^{根節點未被安排的的所有後輩的數量} 然後遞迴每一棵更小的樹
除此之外這道題還可能有環 那麼我們只要開一個數組標記該位置是否被訪問過 然後從祖先節點遍歷 若有一個位置被遍歷了兩遍那就說明存在環 直接輸出0即可

#include<bits/stdc++.h>
#include<unordered_map>
#include<algorithm>
#include<map>
#define ll long long
#define ull unsigned long long
#define IOS ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
using namespace std;
const ll inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const double eps = 1e-4;
const ll N = 2e6 + 5;
const int M = 1e6 + 5;
const ll mod = 998244353;
ll n, m, a[N], fa[N], f1[N], f2[N], num[N];
ll v[N], vis[N], ans;
vector<ll>g[N];
//快速冪
ll ksm(ll base, ll pow) {
	ll ans = 1;
	while (pow) {
		if (pow % 2) ans = ans * base % mod;
		pow /= 2;
		base = base * base % mod;
	}
	return ans;
}
//找父親
ll find(ll x) {
	return x == fa[x] ? x: fa[x] = find(fa[x]);
}
//合併
void merge(ll x, ll y) {
	ll xx = find(x);
	ll yy = find(y);
	if (xx != yy) {
		fa[xx] = yy;
	}
}

ll C(ll up, ll down) {
	return f1[down] * f2[up] % mod * f2[down - up] % mod;
}

bool dfs1(ll x) {
	vis[x] = 1;
	for (int i = 0; i < g[x].size(); i++) {
		if (vis[g[x][i]]) return false;
		if (!dfs1(g[x][i])) return false;
	}
	return true;
}

ll dfs_num(ll x) {
	ll sum = 0;
	for (int i = 0; i < g[x].size(); i++) {
		sum += dfs_num(g[x][i]);
	}
	num[x] = sum + 1;
	return sum + 1;
}

void dfs_gt(ll x) {
	ll nn = num[x] - 1;
	for (int i = 0; i < g[x].size(); i++) {
		ans = ans * C(num[g[x][i]], nn) % mod;
		nn -= num[g[x][i]];
		dfs_gt(g[x][i]);
	}
}

void solve() {
	ans = 1;
	cin >> n >> m;
	ll x, y;
	for (int i = 1; i <= n; i++) {
		fa[i] = i;
	}
        //存圖
	for (int i = 1; i <= m; i++) {
		cin >> x >> y;
		g[y].push_back(x);
		merge(x, y);
	}

	ll sum = n;

	for (int i = 1; i <= n; i++) {
		if (!v[find(i)]) {
                        //判環
			if (!dfs1(find(i))){
				cout << 0 << "\n";
			    return;
			}
                        //存以i為根節點的數的大小
			num[find(i)] = dfs_num(find(i));
                        //深搜找答案
			dfs_gt(find(i));
			ans = (ans * C(num[find(i)], sum)) % mod;
			sum -= num[find(i)];
                        //標記被訪問過
			v[find(i)] = 1;
		}
	}
	cout << ans << "\n";


}
signed main() {
	IOS;
        //f1階乘 f2階乘的逆元
	f1[0] = f2[0] = 1;
	for (ll i = 1; i < 2e6 + 5; i++) {
		f1[i] = f1[i - 1] * i % mod;
		f2[i] = ksm(f1[i], mod - 2);
	}

	int t = 1;
	//cin >> t;
	while (t--)
	{
		solve();
	}
}