Siyuan

「AHOI 2014」支线剧情
「AHOI 2014」支线剧情
扫描右侧二维码阅读全文
02
2019/04

「AHOI 2014」支线剧情

题目链接:LOJ 2226

宅男 JYY 非常喜欢玩 RPG 游戏,比如仙剑,轩辕剑等等。不过 JYY 喜欢的并不是战斗场景,而是类似电视剧一般的充满恩怨情仇的剧情。这些游戏往往都有很多的支线剧情,现在 JYY 想花费最少的时间看完所有的支线剧情。

JYY 现在所玩的 RPG 游戏中,一共有 $n$ 个剧情点,由 $1$ 到 $n$ 编号,第 $i$ 个剧情点可以根据 JYY 的不同的选择,而经过不同的支线剧情,前往 $K_i$ 种不同的新的剧情点。当然 $K_i$ 如果为 $0$,则说明 $i$ 号剧情点是游戏的一个结局了。

JYY 观看一个支线剧情需要一定的时间。JYY 一开始处在 $1$ 号剧情点,也就是游戏的开始。显然任何一个剧情点都是从 $1$ 号剧情点可达的。此外,随着游戏的进行,剧情是不可逆的。所以游戏保证从任意剧情点出发,都不能再回到这个剧情点。

由于 JYY 过度使用修改器,导致游戏的「存档」和「读档」功能损坏了,所以 JYY 要想回到之前的剧情点,唯一的方法就是退出当前游戏,并开始新的游戏,也就是回到 $1$ 号剧情点。JYY 可以在任何时刻退出游戏并重新开始。

不断开始新的游戏重复观看已经看过的剧情很是痛苦,JYY 希望花费最少的时间,看完所有不同的支线剧情。

数据范围:$1 \le n \le 300$,$0 \le K_i \le 50$,$\sum_{i = 1} ^ n K_i \le 5000$。


Solution

刚看到题目可能会想到最小链覆盖,但是发现每条链必须从起点出发,并且边有权值,我们可以尝试往网络流的方向思考。

每条边至少经过 $1$ 次可以转化为这条边的流量下界为 $1$,上界为 $\infty$,费用为花费时间。直接套用有源汇有上下界费用流求解即可。

时间复杂度:$\mathcal O(n\sum K_i f)$。


Code

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>

const int N = 3e2 + 5, M = 2e4 + 5;
const int INF = 0x7f7f7f7f;

int n, m, deg[N], dis[N];
int tot = 1, lnk[N], cur[N], ter[M], nxt[M], cap[M], cost[M];
bool vis[N];

void add(int u, int v, int w, int c) {
    ter[++tot] = v, nxt[tot] = lnk[u], lnk[u] = tot, cap[tot] = w, cost[tot] = c;
}
void addEdge(int u, int v, int w, int c) {
    add(u, v, w, c), add(v, u, 0, -c);
}
bool spfa(int s, int t) {
    memset(dis, 0x7f, sizeof(dis));
    memcpy(cur, lnk, sizeof(lnk));
    std::queue<int> q;
    q.push(s), dis[s] = 0, vis[s] = true;
    while (!q.empty()) {
        int u = q.front();
        q.pop(), vis[u] = false;
        for (int i = lnk[u]; i; i = nxt[i]) {
            int v = ter[i];
            if (cap[i] && dis[v] > dis[u] + cost[i]) {
                dis[v] = dis[u] + cost[i];
                if (!vis[v]) q.push(v), vis[v] = true;
            }
        }
    }
    return dis[t] < INF;
}
int dfs(int u, int t, int flow) {
    if (u == t) return flow;
    vis[u] = true;
    int ans = 0;
    for (int &i = cur[u]; i; i = nxt[i]) {
        int v = ter[i];
        if (cap[i] && !vis[v] && dis[v] == dis[u] + cost[i]) {
            int x = dfs(v, t, std::min(cap[i], flow));
            if (!x) continue;
            cap[i] -= x, cap[i ^ 1] += x, flow -= x, ans += x;
            if (!flow) break;
        }
    }
    vis[u] = false;
    return ans;
}
std::pair<int, int> dinic(int s, int t) {
    int maxFlow = 0, minCost = 0;
    while (spfa(s, t)) {
        int x;
        while ((x = dfs(s, t, INF))) maxFlow += x, minCost += x * dis[t];
    }
    return std::make_pair(maxFlow, minCost);
}
int main() {
    scanf("%d", &n);
    int s = 1, t = n + 1, ans = 0;
    for (int u = 1; u <= n; u++) {
        int k;
        scanf("%d", &k);
        for (int j = 1; j <= k; j++) {
            int v, c;
            scanf("%d%d", &v, &c);
            deg[u]--, deg[v]++, ans += c;
            addEdge(u, v, INF, c);
        }
        addEdge(u, t, INF, 0);
    }
    int ss = 0, tt = n + 2;
    for (int i = 1; i <= n; i++) {
        if (deg[i] > 0) addEdge(ss, i, deg[i], 0);
        if (deg[i] < 0) addEdge(i, tt, -deg[i], 0);
    }
    addEdge(t, s, INF, 0);
    printf("%d\n", ans + dinic(ss, tt).second);
    return 0;
}

发表评论