题解:AT_abc470_f [ABC470F] Googol Swaps
pipilong2024 · · 题解
过 F 不过 C 选手。
我说 A<B<F<<<<<D<C<G<E 有没有懂的。
一种比较好想的建模方式:把
大胆猜测同一个连通块经过无限次交换后能得到原序列的所有排列。证明非常简单,若对于一个新排列,
那么就非常简单了,用并查集跑出每一个连通块,对每一个连通块內部单独算组合数。若一个连通块内有 a,b,z,那么共有
由于交换次数是
Code
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int maxn = 2e5 + 10, mod = 998244353;
int n, m, fa[maxn], sz[maxn];
int cnt[maxn][26];//cnt用于记录每个并查集所包含各个字母的数量
string s;
int fac[maxn], ifac[maxn];
int find(int x) {
return fa[x] == x ? x : fa[x] = find(fa[x]);
}
int qpow(int a, int b) {
int r = 1;
while (b) {
if (b & 1)r = r * a % mod;
a = a * a % mod;
b >>= 1;
}
return r;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
cin >> n >> m >> s, s = "-" + s;
fac[0] = 1;
for (int i = 1; i <= n; i++) fac[i] = fac[i - 1] * i % mod;
ifac[n] = qpow(fac[n], mod - 2);
for (int i = n - 1; i >= 0; i--) ifac[i] = ifac[i + 1] * (i + 1) % mod;//预处理阶乘及其逆元
for (int i = 1; i <= n; i++) {
fa[i] = i, sz[i] = 1;
cnt[i][s[i] - 'a'] = 1;
}//并查集初始化
for (int i = 1, a, b; i <= m; i++) {
cin >> a >> b;
int x = find(a), y = find(b);
if (x == y) continue;
if (sz[x] < sz[y]) swap(x, y);
fa[y] = x, sz[x] += sz[y];
for (int i = 0; i < 26; i++) cnt[x][i] += cnt[y][i];
}
int ans = 1, flag = 0;
for (int i = 1; i <= n; i++) {
if (find(i) != i) continue;
int tot = fac[sz[i]];
for (int c = 0; c < 26; c++) {
tot = tot * ifac[cnt[i][c]] % mod;//除去相同字符内部排列数
if (cnt[i][c] >= 2) flag = 1;
}
ans = ans * tot % mod;
}
if (!flag) ans = ans * qpow(2, mod - 2) % mod;
cout << ans;
return 0;
}