P80023 [CSP-J二十连测第六套 ] --T3--回文(palindrome)

最小回文变换代价

https://www.mxoj.net/problem/P80023

总体思路:

1、先精简问题,考虑不交换,只修改情况下,代价是多少
2、再精简,只考虑修改其中一对对称字母的最小代价是多少

// 计算一对字母 (x,y) 的最小代价
LL calc(int x, int y) {
    if (x == y) return 0; // 相同字母无需代价
    // 三种方式取最小:
    // 1. 把 y 变为 x,代价 v[x]
    // 2. 把 x 变为 y,代价 v[y]
    // 3. 把两者都变为某个字母,代价 mn = min(2*v[c])
    return min({v[x], v[y], mn});
}

那不交换情况下代价就是所有字母对修改代价和。
直接枚举字符串n太大。转化一下,统计不同字母对个数。这样就能更快计算出代价和。
3、考虑交换情况
暴力枚举所有字母对,计算交换代价

代码

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

int n;
LL v[27];        // 每个字母变换到它的代价
LL mn;           // 全局最小的 2*v[i](表示两边都变成某个字母的代价下界)
int t[1000005];  // 字符串转化为数字(1~26)
LL ans, minans;
LL a[27][27];    // a[i][j] 记录字母对 (i,j) 出现次数 (i <= j)

// 计算一对字母 (x,y) 的最小代价
LL calc(int x, int y) {
    if (x == y) return 0; // 相同字母无需代价
    // 三种方式取最小:
    // 1. 把 y 变为 x,代价 v[x]
    // 2. 把 x 变为 y,代价 v[y]
    // 3. 把两者都变为某个字母,代价 mn = min(2*v[c])
    return min({v[x], v[y], mn});
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    cin >> n;
    mn = 1e18;
    // 读入字母代价
    for (int i = 1; i <= 26; i++) {
        cin >> v[i];
        mn = min(mn, 2 * v[i]); // 记录最小的 2*v[i]
    }
    string s;
    cin >> s;
    s = " " + s; // 下标从 1 开始,方便处理

    // 转换字符串为数字序列
    for (int i = 1; i <= n; i++) {
        t[i] = s[i] - 'a' + 1;
    }

    // 统计所有对称位置的字母对
    for (int i = 1; i <= n / 2; i++) {
        int x = t[i], y = t[n - i + 1];
        if (x > y) swap(x, y); // 保证 i<=j 存储
        a[x][y]++;
    }

    // 计算不交换情况下的总代价
    ans = 0;
    for (int i = 1; i <= 26; i++) {
        for (int j = i; j <= 26; j++) {
            ans += calc(i, j) * a[i][j];
        }
    }
    minans = ans; // 初始最优代价为“不交换”

    // 枚举交换两个字母对的情况
    for (int i = 1; i <= 26; i++) {
        for (int j = i; j <= 26; j++) {
            if (!a[i][j]) continue; // 没有这种字母对
            a[i][j]--; // 拿出一个 (i,j) 对
            for (int k = 1; k <= 26; k++) {
                for (int l = k; l <= 26; l++) {
                    if (!a[k][l]) continue; // 没有这种字母对
                    // 原来的代价是 calc(i,j)+calc(k,l)
                    // 交换后可能变为 (i,l)+(j,k) 或 (i,k)+(j,l)
                    minans = min(minans,
                                 ans - calc(i, j) - calc(k, l)
                                     + calc(i, l) + calc(j, k));
                    minans = min(minans,
                                 ans - calc(i, j) - calc(k, l)
                                     + calc(i, k) + calc(j, l));
                }
            }
            a[i][j]++; // 放回 (i,j)
        }
    }

    cout << minans << "\n";
    return 0;
}

代码逻辑总结

所以,这份代码的核心亮点在于:

把所有对称位置压缩为频率表,避免了对 n 的直接操作。

只需要枚举所有可能的字母对组合,就能找到“最优交换”效果。

因为字母只有 26 个,复杂度几乎是常数,非常优雅。

posted @ 2025-09-05 16:31  katago  阅读(15)  评论(0)    收藏  举报