poj3680(最小费用流 + 拆点)

这篇博客介绍了一种利用最小费用流算法解决区间覆盖问题的方法。具体来说,给定一组带权重的开区间,目标是选择不超过k个区间,使得任何点都不被超过k个区间覆盖,同时最大化权重总和。博主通过将权重视为负值,构建网络图,设置超级源点和超级汇点,并连接所有相关节点,确保图的连通性。最后应用最小费用流算法求解,当无法从起点到达终点时提前返回结果。该方法能够有效地找到符合条件的最大权重和。
摘要由CSDN通过智能技术生成

(又是一道简单的最小费用流的题目)
题目大概意思为有N个带权的开区间,现在从中选取一些区间,使任意点都不能被超过k个区间覆盖,求最大权重和

首先我们求得是最大的权重和,所以我们把 权重 ,看为 -权重 ,来求最小费,最后求个绝对值即可

然后每个区间只能选一次,考虑拆点,每个点拆为两个,连接一条容量为1,费用为 -权重

设置一个超级源点和超级汇点连接每个点,边的容量为1,费用为0(因为不确定从哪个区间开始覆盖,和从哪个区间结束)
将两个不相交(b[i] <= a[i])的区间相连,容量为1,费用为0(这样便可以正确连接图上有关系的点)

设置流最大为k,即意思为不能被超过k个区间覆盖

最后套用最小费用流模板即可
(注意当dist[to] == INF,即起点无法到终点时,提前返回res即可)

#include <stdio.h>
#include <iostream>
#include <vector>
#define INF 1000000005
using namespace std;
int n, k;
int a[205], b[205], w[205];
struct edge
{
    int to, cap, coust, rev;
    edge(int t, int c, int s, int r)
    {
        to = t; cap = c; coust = s; rev = r;
    }
};
vector<struct edge> ddd[505];
int dist[505];
int prev[505];
int prep[505];
void add_edge(int from, int to, int cap, int coust)
{
    ddd[from].push_back(edge(to, cap, coust, ddd[to].size()));
    ddd[to].push_back(edge(from, 0, -coust, ddd[from].size() - 1));
}
int min_coust(int from, int to, int flow)
{
    int res = 0;
    while(flow > 0)
    {
        fill(dist, dist + n + n + 2, INF);
        dist[from] = 0;
        bool flag = true;
        while(flag)
        {
            flag = false;
            for(int i = 0; i < n + n + 2; i++)
            {
                if(dist[i] != INF)
                {
                    for(int j = 0; j < ddd[i].size(); j++)
                    {
                        struct edge e = ddd[i][j];
                        if(e.cap != 0 && dist[i] + e.coust < dist[e.to])
                        {
                            flag = true;
                            dist[e.to] = dist[i] + e.coust;
                            prev[e.to] = i;
                            prep[e.to] = j;
                        }
                    }
                }
            }
        }
        if(dist[to] == INF)
        {
            return res;
        }
        int d = flow;
        for(int i = to; i != from; i = prev[i])
        {
            d = min(d, ddd[prev[i]][prep[i]].cap);
        }
        for(int i = to; i != from; i = prev[i])
        {
            ddd[prev[i]][prep[i]].cap -= d;
            ddd[i][ddd[prev[i]][prep[i]].rev].cap += d;
        }
        res = res - dist[to] * d;
        flow = flow - d;
    }
    return res;
}
int main()
{
    int N;
    scanf("%d", &N);
    while(N--)
    {
        for(int i = 0; i < 2 * n + 2; i++)
        {
            ddd[i].clear();
        }
        scanf("%d %d", &n, &k);
        for(int i = 1; i <= n; i++)
        {
            scanf("%d %d %d", &a[i], &b[i], &w[i]);
        }
        for(int i = 1; i <= n; i++)
        {
            add_edge(0, i, 1, 0);
            add_edge(i, n + i, 1, -w[i]);
            add_edge(n + i, n + n + 1, 1, 0);
            for(int j = i + 1; j <= n; j++)
            {
                if(b[i] <= a[j])
                {
                    add_edge(i + n, j, 1, 0);
                }
                else if(a[i] >= b[j])
                {
                    add_edge(j + n, i, 1, 0);
                }
            }
        }
        printf("%d\n", min_coust(0, n + n + 1, k));
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值