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();
}
}

浙公网安备 33010602011771号