Codeforces ECR #31 F. Anti-Palindromize

解法

最小費用流で解ける.
まず,各アルファベットに対応する26個の頂点と,N / 2 個からなる文字列の前半(ないし後半)に対応する頂点をもつグラフをつくる.
それぞれのアルファベットから,文字列の各位置に対して辺を張る.
以下ではアルファベットの頂点番号を N / 2 + (c - 'a') とする.
頂点 N / 2 + (c - 'a') から頂点 i (0 <= i < N / 2) にフローが流れることを,S[i] または S[N - 1 - i] にアルファベット c を配置するということに対応付ける.
こうする時,辺のコストは max(S[i] == c ? b[i] : 0, S[N - 1 - i] == c ? b[N - 1 - i] : 0) とすることができる.
もちろん容量は1である.
source からは各アルファベットに対して辺を張り,コストは0,容量はそれぞれのアルファベットの出現数とする.
また,文字列の各位置に対応する頂点から sink に辺を張るのだが,これはコスト0,容量2としておく.
以上のように辺をはると,同じ頂点 i (0 <= i < N / 2) に対して同じ文字は割り当てられず,それぞれの頂点にちょうど2個のアルファベットを割り当てることができる.

ソースコード

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

using ll = long long;

using weight = int;

constexpr int INF = 1e9;

struct edge {
    int to, cap;
    weight cost;
    int rev;
};

using edges = std::vector<edge>;
using graph = std::vector<edges>;

void add_edge(graph& g, int from, int to, int cap, weight cost) {
    g[from].push_back(edge{to, cap, cost, (int)g[to].size()});
    g[to].push_back(edge{from, 0, -cost, (int)g[from].size()-1});
}

weight min_cost_flow(graph& g, int s, int t, weight f) {
    using P = std::pair<weight, int>;
    weight res = 0;
    std::vector<weight> h(g.size(), 0);
    std::vector<weight> dist(g.size());
    std::vector<int> prevv(g.size()), preve(g.size());
    while(f > 0) {
        std::priority_queue<P, std::vector<P>, std::greater<P>> que;
        std::fill(dist.begin(), dist.end(), INF);
        dist[s] = 0;
        que.push(P{dist[s], s});
        while(!que.empty()) {
            P p = que.top(); que.pop();
            int v = p.second;
            if(dist[v] < p.first) {
                continue;
            }
            for(int i = 0; i < (int)g[v].size(); ++i) {
                edge& e = g[v][i];
                if(e.cap > 0 && dist[e.to] > dist[v] + e.cost + h[v] - h[e.to]) {
                    dist[e.to] = dist[v] + e.cost + h[v] - h[e.to];
                    prevv[e.to] = v;
                    preve[e.to] = i;
                    que.push(P{dist[e.to], e.to});
                }
            }
        }
        if(dist[t] == INF) {
            return -1;
        }
        for(int v = 0; v < (int)g.size(); ++v) {
            h[v] += dist[v];
        }

        weight d = f;
        for(int v = t; v != s; v = prevv[v]) {
            d = min(d, g[prevv[v]][preve[v]].cap);
        }
        f -= d;
        res += d * h[t];
        for(int v = t; v != s; v = prevv[v]) {
            edge& e = g[prevv[v]][preve[v]];
            e.cap -= d;
            g[v][e.rev].cap += d;
        }
    }
    return res;
}


int main() {
    int N;
    string S;
    cin >> N >> S;
    vector<int> b(N);
    for(int i = 0; i < N; ++i) {
        cin >> b[i];
    }
    vector<int> cnt(26);
    for(auto c : S) {
        cnt[c - 'a']++;
    }

    graph g(N / 2 + 26 + 2);
    int const source = N / 2 + 26;
    int const sink = source + 1;
    for(int i = 0; i < 26; ++i) {
        add_edge(g, source, N / 2 + i, cnt[i], 0);
    }
    for(int i = 0; i < N / 2; ++i) {
        add_edge(g, i, sink, 2, 0);
        for(int j = 0; j < 26; ++j) {
            int cost = 0;
            if(S[i] - 'a' == j) {
                cost = max(cost, b[i]);
            }
            if(S[N - 1 - i] - 'a' == j) {
                cost = max(cost, b[N - 1 - i]);
            }
            add_edge(g, N / 2 + j, i, 1, -cost);
        }
    }

    cout << -min_cost_flow(g, source, sink, N) << endl;
}