【洛谷2469/BZOJ1927】[SDOI2010]星际竞速(费用流/最小路径覆盖)

本文介绍了一种利用费用流算法解决带权DAG最小路径覆盖问题的方法,通过构造网络流模型并加入额外费用,实现了对路径起点权重的处理。

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

题目:

洛谷2469

分析:

把题目翻译成人话:给一个带边权的DAG,求一个路径覆盖方案使路径边权总和最小。从点 i i 开始的路径需要额外加上Ai的权值。
回xian忆chang一xue下xi不带权DAG的最小路径覆盖用网络流是怎么做的:把点 u u 拆成u u u ′ 两个点,如果原图存在边 (u,v) ( u , v ) 就在网络中连边 (u,v) ( u , v ′ ) ,然后源点 s s 向所有u连边,所有 v v 向汇点t连边,所有边容量均为 1 1 ,跑最大流。原图中点数n减去最大流就是最小路径覆盖。
考虑这样做的原理:最小路径覆盖中每个点属于且仅属于一条路径。对于任意包含多于 1 1 个点的路径,有且只有一个点入度为0,一个点出度为 0 0 ,其余所有点入度、出度皆为1。网络中若 (s,u) ( s , u ) 有流量说明 u u 出度为1 s s 流到u后必然要流向某个点 v v ′ ),若 (u,t) ( u ′ , t ) 有流量说明 u u 入度为1(必须有某个点流到 u u ′ (u,t) ( u ′ , t ) 才有流量)。那么最大流就是所有点入度之和, n n 减去入度之和就是入度为0的点数,即路径起点的数量,即路径数。如果边 (u,v) ( u , v ′ ) 有流量说明原图中 (u,v) ( u , v ) 这条边在最小路径覆盖中。
那么给每条边加上权以后呢?自然能想到把原图中边 (u,v) ( u , v ) 的权作为网络中 (u,v) ( u , v ′ ) 的费用然后跑费用流

然后你就gg了

费用流全名叫“最小费用最大流”,是在保证流量最大的情况下的最小费用。在这道题中,就相当于首先要保证路径的条数最少(即流量最大),然后再使费用最小。此时要感谢某神犇T兔z崽z子给我说的我自己想出来的方法……先打个广告

戳我进入兔崽子的博客

s s 向每个u连边,这条边有流量说明 u u 入度为0(因为 (u,t) ( u ′ , t ) 容量为 1 1 (s,u)有流量就说明肯定没有别的点会流到 u u ′ )。这样无论如何路径覆盖最大流都是 n n ,就消除了路径条数的影响,求出最小费用即为答案。
这种方案顺便解决了下一个问题:路径起点有额外权值。把Au作为 (s,u) ( s , u ′ ) 的费用即可。

代码:

#include <cstdio>
#include <algorithm>
#include <cstring>
#include <iostream>
#include <queue>
using namespace std;
namespace zyt
{
    typedef long long ll;
    const ll INF = 0x3f3f3f3f3f3f3f3fLL;
    const int N = 810, P = N * 2, M = (N * 3 + 15010) * 2;
    int head[P], cnt, n, m, s, t;
    struct edge
    {
        int to, w, c, next;
    }e[M];
    inline void add(const int a, const int b, const int c, const int d)
    {
        e[cnt] = (edge){b, c, d, head[a]};
        head[a] = cnt++;
    }
    inline void addtw(const int a, const int b, const int c, const int d)
    {
        add(a, b, c, d);
        add(b, a, 0, -d);
    }
    namespace EK
    {
        bool vis[P];
        ll dis[P];
        int pre[P];
        bool SPFA()
        {
            queue<int>q;
            memset(pre, -1, sizeof(pre));
            memset(vis, 0, sizeof(vis));
            memset(dis, INF, sizeof(dis));
            q.push(s);
            vis[s] = true;
            dis[s] = 0;
            while (!q.empty())
            {
                int u = q.front();
                q.pop();
                vis[u] = false;
                for (int i = head[u]; ~i; i = e[i].next)
                {
                    int v = e[i].to;
                    if (e[i].w && dis[v] > dis[u] + e[i].c)
                    {
                        dis[v] = dis[u] + e[i].c, pre[v] = i;
                        if (!vis[v])
                            vis[v] = true, q.push(v);
                    }
                }
            }
            return dis[t] != INF;
        }
        ll EK()
        {
            ll ans = 0;
            while (SPFA())
            {
                int i = pre[t];
                int minn = n + 1;
                while (~i)
                    minn = min(minn, e[i].w), i = pre[e[i ^ 1].to];
                i = pre[t];
                while (~i)
                    e[i].w -= minn, e[i ^ 1].w += minn, i = pre[e[i ^ 1].to];
                ans += minn * dis[t];
            }
            return ans;
        }
    }
    int work()
    {
        ios::sync_with_stdio(false);
        memset(head, -1, sizeof(head));
        cin >> n >> m;
        s = n * 2 + 1, t = n * 2 + 2;
        for (int i = 1; i <= n; i++)
        {
            int a;
            cin >> a;
            addtw(s, i, 1, 0);
            addtw(s, i + n, 1, a);
            addtw(i + n, t, 1, 0);
        }
        for (int i = 0; i < m; i++)
        {
            int a, b, c;
            cin >> a >> b >> c;
            if (a > b)
                swap(a, b);
            addtw(a, b + n, 1, c);
        }
        cout << EK::EK();
        return 0;
    }
}
int main()
{
    return zyt::work();
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值