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;
}
posted @ 2026-09-25 09:26  thenrify  阅读(9)  评论(0)    收藏  举报