hihoCoder #1902 字符替换

解法

这题比赛时过的人很多,我却没思路,糊里糊涂写了个强联通分量,得了 80 分。

这题思路是这样的。

一个替换操作可以看做一个有向边,所以题目实际上给出了一个有向图 \(G\),一个节点代表一个字母。

注意题目要求每个操作都必须执行一次。

关于自环

首先注意到自环是没有意义的,因此处理输入时把自环忽略掉。

这里需要特别说明自环的问题,题目描述中并没有说明 \(X_i \ne Y_i\)。不过似乎可以合理地假设输入中不存在 \(X_i = Y_i\) 的操作。

有些 AC 的代码并没有判断自环,比如冰心水蜜桃的提交。当输入中有自环时,这个代码是有 BUG 的。

关于重边

实际上重边也是没有意义的,但是我们不必特别处理它。


\(\mathsf{h}\) 表示 \(G\) 中对应于字符 ‘h’ 的节点。

设字符 \(x\) 在串 \(S\) 中出现过且 \(x\) 不是 ‘h’ 将 \(x\) 出现的次数记做 \(c_x\) 。则这 \(c_x\)\(x\) 能转变为 ‘h’ 的充要条件是「图 \(G\) 中存在一条从 \(x\) 到 ‘h’ 的简单路径」。

证明:不失一般性,设 \(x\to x_1 \to x_2 \to \mathsf{h}\) 是一条从 \(x\) 到 ‘h’ 的简单路径,则我们可以按下述方法将 \(x\) 变为 ‘h’。

首先将 \(x\) 变为 \(x_1\),这个操作用掉了 \((x\to x_1)\) 这条边;再将剩下的从 \(x\) 发出的边全部用掉,这些边将不换改变当前的字符串,然后把所有从 \(x\) 发出的边从图 \(G\) 中删除。以此类推。

不难注意到若 ‘h’ 有出边,则上述论证是有问题的。

‘h’ 的出度为零的情形是平凡的。考虑 ‘h’ 的出度不为零的情形。此时若图 \(G\) 中不存在「从 ‘h’ 到 ‘h’ 的回路」,则若初始字符串 \(S\) 中有 ‘h’,则这些 ‘h’ 终将变成别的字符。因此在这种情况下我们可以将 ‘h’ 的所有出边先执行一遍,并把这些边从图 \(G\) 中删除。

这样就完成了上述论证。

不过至此我们只是针对一个字符 \(x\) 进行论证。实际上对多个字符,结论是一样的,证明留给读者。


现在来考虑图 \(G\) 中存在从 ‘h’ 到 ‘h’ 的回路的情形。注意这样的回路一定不是自环。任取一个从 ‘h’ 到 ‘h’ 的回路 \(C\),我们可以先把 'h' 变成回路 \(C\) 上 'h' 的后继,得到一个新字符串 \(S'\),并把图 \(G\) 中其他的 ‘h’ 的出边删除。这样就把问题规约为上一段所描述的情形。

实现

我们需要判断的是,对于字符 \(x\in S\)\(x\ne\mathsf{h}\),图 \(G\) 中是否有一条从 \(x\)\(\mathsf{h}\) 的简单路径,这可以通过 DFS 完成。另外当 \(\mathsf{h}\) 的出度不为零时我们需要判断图 \(G\) 中是否存在一条从 \(\mathsf{h}\)\(\mathsf{h}\) 的回路。先进行DFS,确保字符串 \(S\) 中所有字符都被访问过。遍历每一条以 \(\mathsf{h}\) 为起点的边 \((\mathsf{h} \to x)\),判断图 \(G\) 中是否存在从 \(x\)\(\mathsf{h}\) 的简单路径。

也可以把边反向以后建图,这样只要对 \(\mathsf{h}\) 调用一次 DFS 就可以了。

Implementation

#include <bits/stdc++.h>
using namespace std;

int main() {
    //freopen("main.in", "r", stdin);
    int n; cin >> n;
    string s; cin >> s;
    vector<int> cnt(26);
    for(auto ch: s) cnt[ch-'a']++;
    vector<vector<int>> g(26);
    vector<bool> to_h(26);
    bool flag = false;
    while (n--) {
        char x, y; cin >> x >> y;
        if (x != y) {
            g[y-'a'].push_back(x-'a');
            if(x == 'h') {
                to_h[y-'a'] = true;
                flag = true;
            }
        }
    }
    
    function<void(int)> dfs;
    vector<bool> vis(26);
    dfs = [&](int u) {
        vis[u] = true;
        for(auto v: g[u]) {
            if(!vis[v]) dfs(v);
        }
    };
    int h = 'h' - 'a';
    dfs(h);
    
    int ans = 0;
    for (int i = 0; i < 26; i++)
        if(vis[i]) ans += cnt[i];
    if (flag) {
        for (int i = 0; i < 26; i++)
            if (to_h[i] && vis[i]) {
                flag = false;
                break;
            }
        if (flag) ans -= cnt[h];
    }
    cout << ans << endl;
    return 0;
}
posted @ 2018-12-24 21:26  Pat  阅读(217)  评论(0编辑  收藏  举报