Loading

P6700 [PA 2015 Final] Edycja

很神的题。内容参考了其它题解。

首先有两个显然的结论:

  1. 先做完所有操作二再做操作一一定更优
  2. 一个字母最多进行一次操作二

\(n = 10^6\) 的数据范围难以下手,但是时刻不能忘记字符串有一个天然的小数据范围:\(|\Sigma| = 26\)。而这题又有一个统一替换的操作,且根据上面的性质,我们可以考虑枚举每个字母 \(a\) 操作二后变成的字母 \(b\),代价为 \(\sum [s_i = a][t_i \neq b]\),即先把 \(a\) 变成 \(b\) 再逐个单点修改。如果 \(a \neq b\) 就需要多加 \(c\) 的代价。

枚举每对 \(a, b\) 仍然会爆,但是由于 \(a\) 只会变成唯一的 \(b\),我们不妨换个角度思考,把所有字母组成一个有向图,\(a \rightarrow b\) 的边权就是 \(a\) 变为 \(b\) 的代价,记为 \(w(a, b)\)。那么要求就变成了选出一个基环树森林。

建完图之后如何刻画?假设每个字母 \(c\) 所对应的节点上有若干棋子,\(i\) 所代表的棋子在 \(s_i\) 对应的节点上,那么一次走向 \(a \rightarrow b\) 的边就代表着将 \(a\) 上的棋子都转移到 \(b\) 上,花费边权的代价,但是我们需要保证每个棋子只转移一次,即都恰好走向其所在节点的后继节点。那这其实就是一个拓扑排序的过程。

考虑对于一个已有的基环森林怎么计算。

  1. 一个自环,没有代价。

  2. 树(根节点有一个自环的基环树),我们按照拓扑逆序加,先转移 \(dep = 1\) 的点到根节点,再转移 \(dep = 2\) 的点,以此类推就好了,代价为边权和。

  3. 基环树(不是环),这时上面的策略会产生冲突,毕竟只有 DAG 才能拓扑排序。那么考虑在环上找一个有子树的点,设这个点为 \(a\),其环上的前驱为 \(b\),子树中一个叶子为 \(d\),那么我们先花费 \(c\) 的代价把 \(b\) 上的棋子放到 \(d\) 上,那么就有一个空的节点了,然后把环上的点跑一遍拓扑排序,最后肯定是 \(a\) 空着了,就把 \(b\) 转移到 \(a\) 上,同样花费 \(w(a, b)\),然后就剩下若干棵树,直接跑就行。由此可见,基环树会多 \(c\) 的代价。

  4. \(\ge 2\) 个点的环,那么找不到叶子了,我们就考虑先看看森林里有没有其它有叶子的树/基环树,如果有就花费 \(c\) 的代价转移一个点,再转移回来,也会多花费 \(c\) 的代价。如果外面也没有叶子,那么这种情况就没有解了,需要调整。

总结一下,就是遇到环,则代价会多 \(c\)

那么最显然的贪心就是对于 \(a\),考虑向 \(w(a, b)\) 最小的 \(b\) 连边,那么这个贪心爆掉的可能就是由于环多出的哪些 \(c\) 可以优化。那么考虑在这个图上进行一些修改。

这里还有个结论:不按照这样连边,如果出现原来没有的环一定不优,因为如果有新环,肯定有一条边 \((a, b)\) 和原来不同,如果改掉这条边环的个数一定不会增多(这是个基环树),且边权和一定会变小,所以不优。

然后思路就显然了,点数为 \(|\Sigma| = 26\)\(\ge 2\) 个点的环只有 \(13\) 个,直接考虑状压掉:\(dp_{i, S, 0/1}\) 表示考虑为 \(i\) 找其连向的点,当前已经被断开的环为的集合为 \(S\),当前森林里是否有多的叶子节点,暴力转移即可。

时间复杂度 \(\large{\mathcal{O}(|\Sigma|^22^{\frac{|\Sigma|}{2}})}\)

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
// typedef __int128 i128;
typedef pair<int, int> pii;
const int N = 1e6 + 10, M = 26, mod = 998244353;
template<typename T>
void dbg(const T &t) { cout << t << endl; }
template<typename Type, typename... Types>
void dbg(const Type& arg, const Types&... args) {
    cout << arg << ' ';
    dbg(args...);
}
namespace Loop1st {
#define popc(s) (__builtin_popcount(s))
void cmin(ll &x, ll y) { if (x > y) x = y; }
int n, c, w[M + 1][M + 1], b[M + 1], to[M + 1], in[M + 1], notok = 1, tot;
char s1[N], s2[N];
ll dp[M + 1][1 << 13][2];
void main() {
    cin >> n >> c >> (s1 + 1) >> (s2 + 1);
    for (int i = 1; i <= n; i++) w[s1[i] - 'a'][s2[i] - 'a']++;
    for (int i = 0; i < M; i++) {
        b[i] = -1;
        int sum = 0;
        for (int j = 0; j < M; j++) sum += w[i][j];
        for (int j = 0; j < M; j++) {
            w[i][j] = sum - w[i][j];
            if (i != j) w[i][j] += c;
            if (w[i][j] < w[i][to[i]]) to[i] = j;
        }
        in[to[i]]++;
    }
    queue<int>q;
    for (int i = 0; i < M; i++) if (!in[i]) q.push(i), notok = 0;
    while (!q.empty()) {
        int u = q.front(); q.pop();
        if (!--in[to[u]]) q.push(to[u]);
    }
    for (int i = 0; i < M; i++) if (in[i] && i != to[i]) {
        int u = i;
        while (in[u]) in[u] = 0, b[u] = tot, u = to[u];
        tot++;
    }
    if (!tot) {
        ll ans = 0;
        for (int i = 0; i < M; i++) ans += w[i][to[i]];
        cout << ans << '\n';
        return ;
    }
    memset(dp, 0x3f, sizeof dp);
    dp[0][0][0] = 0;
    for (int i = 0; i < M; i++) for (int o = 0; o < 2; o++) {
        for (int s = 0; s < (1 << tot); s++) {
            for (int j = 0; j < M; j++) {
                int t = s;
                if (b[i] != -1 && to[i] != j) t |= (1 << b[i]);
                if (b[j] != -1 && (to[i] != j || b[i] != b[j])) t |= (1 << b[j]);
                cmin(dp[i + 1][t][o | (to[i] != j)], dp[i][s][o] + w[i][j]);
            }
        }
    }
    ll ans = 1ll << 60;
    for (int s = 0; s < (1 << tot); s++) for (int o = notok; o < 2; o++) cmin(ans, dp[M][s][o] + (ll)c * (tot - popc(s)));
    cout << ans << '\n';
}

}
int main() {
    // freopen("data.in", "r", stdin);
    // freopen("data.out", "w", stdout);
    ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    int T = 1;
    // cin >> T;
    while (T--) Loop1st::main();
    return 0;
}
posted @ 2026-01-13 16:18  循环一号  阅读(2)  评论(0)    收藏  举报