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();
	}
}
posted @ 2022-05-30 18:27  Yaqu  阅读(94)  评论(0)    收藏  举报