Googol Swaps
Googol Swaps
给定一个长度为 N 的字符串 S,由小写英文字母组成。
你可以进行以下操作恰好 10^100 次:
- 选择一个整数 i(1 ≤ i ≤ M),交换 S 中第 A_i 个字符和第 B_i 个字符。
求经过这些操作后,S 可能变成的不同字符串的数量,答案对 998244353 取模。
约束条件
- N, M 是整数
- 2 ≤ N ≤ 2×10^5
- 1 ≤ M ≤ 2×10^5
- S 是长度为 N 的小写字母字符串
- 1 ≤ A_i < B_i ≤ N
- 所有 (A_i, B_i) 两两不同
1. 图论建模
- 将字符串的 N 个位置看作 N 个顶点。
- 每次操作交换一条边 (A_i, B_i) 两端的字符,因此字符只能在同一个连通分量内移动。
- 重要结论:每个连通分量内的字符集合始终保持不变。
2. 置换与奇偶性
- 用排列 p 描述最终状态:p_i 表示原来在位置 i 的字符最终所在的位置。
- 一次交换对应一个对换,会改变排列的奇偶性(奇偶互换)。
- 总共执行 10^100 次,该数是偶数,所以最终排列必须是偶排列。
- 反过来,任意满足“分量内字符集合不变”且为偶排列的状态,都可以用不超过 N^2 次操作实现,并且可以通过重复执行某个操作(偶数次)补足到恰好 10^100 次。
因此,可达状态 ⇔ 偶排列且满足分量内集合不变。
3. 计数策略
3.1 独立计算各连通分量
对于每个连通分量,假设其大小为 s,其中各字母出现次数为 c₁, c₂, …,则该分量内部可形成的不同字符串数为:
\[\frac{s!}{c_1! \cdot c_2! \cdots}
\]
将所有分量的这个值相乘,得到满足“分量内字符集合不变”的总排列数(即所有可能的字符分配方案)。
3.2 考虑偶排列限制
-
情况一:存在某个连通分量含有重复字母(某个字母出现次数 ≥2)
在该分量内,交换两个相同字母的位置,字符排列不变,但对应的置换奇偶性会翻转。
因此,该分量内任意一种字符排列都能对应到一个偶置换(通过调整相同字母的交换次数)。
此时,所有满足集合不变的字符排列均可通过偶排列实现,答案即为总排列数。 -
情况二:所有连通分量内字母均互不相同(每个字母在每个分量中最多出现一次)
此时,每个字符排列唯一对应一个置换,且没有重复字母可用于调整奇偶性。
偶置换与奇置换成对出现(例如取任意两个位置交换它们的像),故偶置换恰好占总排列数的一半。
因此答案需乘以 1/2(模意义下乘以 2 的逆元)。
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const ll MOD = 998244353;
ll modpow(ll a, ll e) {
ll r = 1;
while(e) {
if(e & 1) r = r * a % MOD;
a = a * a % MOD;
e >>= 1;
}
return r;
}
void solve() {
int n, m; cin >> n >> m;
string s; cin >> s;
vector<int> parent(n + 1), sz(n + 1, 1);
iota(parent.begin(), parent.end(), 0);
function<int(int)> find = [&](int x) {
if(parent[x] == x) return x;
return parent[x] = find(parent[x]);
};
auto unite = [&](int a, int b) {
int af = find(a), bf = find(b);
if(af == bf) return;
if(sz[af] < sz[bf]) swap(af, bf);
parent[bf] = af;
sz[af] += sz[bf];
};
for(int i = 1; i <= m; i ++) {
int a, b; cin >> a >> b;
unite(a, b);
}
unordered_map<int, unordered_map<char, int>> comp;
for(int i = 1; i <= n; i ++) {
int r = find(i);
comp[r][s[i - 1]] ++;
}
vector<long long> fact(n + 1), invfact(n + 1);
fact[0] = 1;
for (int i = 1; i <= n; i ++) fact[i] = fact[i - 1] * i % MOD;
invfact[n] = modpow(fact[n], MOD - 2);
for(int i = n; i >= 1; i --) invfact[i - 1] = invfact[i] * i % MOD;
long long ans = 1;
bool flag = false;
for(auto &k : comp) {
auto &mp = k.second;
int total = 0;
for(auto &p : mp) total += p.second;
long long ways = fact[total];
for(auto &p : mp) {
ways = ways * invfact[p.second] % MOD;
if(p.second >= 2) flag = true;
}
ans = ans * ways % MOD;
}
if(!flag) ans = ans * modpow(2, MOD - 2) % MOD;
cout << ans << '\n';
}
int main() {
ios::sync_with_stdio(false); cin.tie(0);
int t = 1; //cin >> t;
while(t --) solve();
return 0;
}

浙公网安备 33010602011771号