H Permutation Counting(並查集 + dfs)
阿新 • • 發佈:2022-05-30
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(); } }