[上下界有源汇最小费用可行流] BZOJ 3876: [Ahoi2014&Jsoi2014]支线剧情

本文介绍了一个基于无源汇最小费用可行流算法的游戏剧情探索问题解决方案。通过构建特殊的图模型,并运用SPFA算法寻找最短路径,实现了游戏所有剧情点的最小时间消耗遍历。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Description

JYY现在所玩的RPG游戏中,一共有N个剧情点,由1N编号,第i个剧情点可以根据JYY的不同的选择,而经过不同的支线剧情,前往Ki种不同的新的剧情点。当然如果为0,则说明i号剧情点是游戏的一个结局了。
JYY观看一个支线剧情需要一定的时间。JYY一开始处在1号剧情点,也就是游戏的开始。显然任何一个剧情点都是从1号剧情点可达的。此外,随着游戏的进行,剧情是不可逆的。所以游戏保证从任意剧情点出发,都不能再回到这个剧情点。由于JYY过度使用修改器,导致游戏的“存档”和“读档”功能损坏了,
所以JYY要想回到之前的剧情点,唯一的方法就是退出当前游戏,并开始新的游戏,也就是回到1号剧情点。JYY可以在任何时刻退出游戏并重新开始。不断开始新的游戏重复观看已经看过的剧情是很痛苦,JYY希望花费最少的时间,看完所有不同的支线剧情。

Solution

还是先考虑上下界无源汇最小费用可行流。
一条uv边权上下界为(w,L,R)的边,可以建超级源S和超级汇T,连Sv边权上下界为(w,0,L)的边,连uT边权上下界为(0,0,L)的边,连uv边权上下界为(w,0,RL)的边。在这个图上跑最小费用最大流,若存在解的话,一定能在新建的边上跑满流。
而如果有源汇的话还是套路,连一条TS边权上下界为(0,0,+)的边就好了。

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

const int N = 1010;
const int INF = 0x3f3f3f3f;

inline char get(void) {
    static char buf[100000], *S = buf, *T = buf;
    if (S == T) {
        T = (S = buf) + fread(buf, 1, 100000, stdin);
        if (S == T) return EOF;
    }
    return *S++;
}
template<typename T>
inline void read(T &x) {
    static char c; x = 0; int sgn = 0;
    for (c = get(); c < '0' || c > '9'; c = get()) if (c == '-') sgn = 1;
    for (; c >= '0' && c <= '9'; c = get()) x = x * 10 + c - '0';
    if (sgn) x = -x;
}

struct edge {
    int to, next, cap, cost;
    edge (int t = 0, int n = 0, int ca = 0, int co = 0) {
        to = t; next = n; cap = ca; cost = co;
    }
};
edge G[N * N * 2];
int head[N];
int vis[N], dis[N], pre[N];
int Gcnt, n, m, x, y;
int S, T, SS, TT, ans;
queue<int> Q;

inline void AddEdge(int from, int to, int cap, int cost) {
    G[++Gcnt] = edge(to, head[from], cap, cost); head[from] = Gcnt;
    G[++Gcnt] = edge(from, head[to], 0, -cost); head[to] = Gcnt;
}
inline void Del(int u) {
    for (int i = head[u]; i; i = G[i].next)
        G[i].cap = G[i ^ 1].cap = 0;
}
inline bool SPFA(int S, int T) {
    memset(dis, 0x3f, sizeof dis);
    Q.push(S); vis[S] = true; dis[S] = 0;
    while (!Q.empty()) {
        x = Q.front(); Q.pop(); vis[x] = false;
        for (int i = head[x]; i; i = G[i].next) {
            edge &e = G[i];
            if (e.cap && dis[x] + e.cost < dis[e.to]) {
                dis[e.to] = dis[x] + e.cost;
                pre[e.to] = i ^ 1;
                if (!vis[e.to]) {
                    vis[e.to] = true;
                    Q.push(e.to);
                }
            }
        }
    }
    return dis[T] != INF;
}
inline int Flow(int S, int T) {
    int Cost = 0, f;
    while (SPFA(S, T)) {
        f = INF;
        for (int i = T; i != S; i = G[pre[i]].to)
            f = min(f, G[pre[i] ^ 1].cap);
        for (int i = T; i != S; i = G[pre[i]].to) {
            G[pre[i]].cap += f; G[pre[i] ^ 1].cap -= f;
        }
        Cost += f * dis[T];
    }
    return Cost;
}

int main(void) {
    freopen("1.in", "r", stdin);
    read(n); Gcnt = 1;
    S = 1; T = n + 1; SS = n + 2; TT = n + 3;
    for (int i = 1; i <= n; i++) {
        read(m);
        if (i != 1) AddEdge(i, T, INF, 0);
        while (m--) {
            read(x); read(y);
            AddEdge(i, x, INF, y);
            AddEdge(SS, x, 1, y);
            AddEdge(i, TT, 1, 0);
        }
    }
    AddEdge(T, S, INF, 0);
    ans = Flow(SS, TT);
    printf("%d\n", ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值