[网络流 24 题] 最长 k 可重区间集

题目描述

给定实直线 L 上 n 个开区间组成的集合 I,和一个正整数 k,试设计一个算法,从开区间集合 I中选取出开区间集合 S⊆I S \subseteq I S⊆I,使得在实直线 L L L 的任何一点 x ,S 中包含点 x 的开区间个数不超过 k 。且 ∑z∈S∣z∣达到最大。这样的集合 S 称为开区间集合 I 的最长 k 可重区间集。∑z∈S∣z∣ 称为最长 k 可重区间集的长度。

对于给定的开区间集合 I 和正整数 k ,计算开区间集合 I 的最长 k 可重区间集的长度。
输入格式

文件的第 1 行有 2 个正整数 n 和 k ,分别表示开区间的个数和开区间的可重迭数。
接下来的 n 行,每行有 2 个整数 li 和 ri ,表示开区间的左右端点坐标,注意可能有 li>ri ,此时请将其交换。
输出格式

输出最长 k 可重区间集的长度。
样例
样例输入

4 2
1 7
6 8
7 10
9 13

样例输出

15

数据范围与提示

1≤n≤500
1≤k≤3

题解
这道题实际上就是给出n个区间,求k个重叠以内的最大覆盖范围(两个区间同时覆盖算两次)
这题刚开始看的时候觉得并不能用网络流解,因为这道题的限制是区间不重叠,很难用网络流来表示这种关系
实际上这道题可以用网络流的最大费用最大流来解,用容量来限制重叠的个数,用费用来表示覆盖的范围,建图实际上是建一个二分图。
将区间按照左端点排序,然后将区间拆成两个点,分别代表左端点和右端点,其中每个右端点与汇点连一条容量为1,费用为0的边,源点与每个左端点连一条容量为1, 费用为0的边,每个对应的左端点和右端点连接一条容量为1,费用为区间的大小的边。
对于每个右节点,将他与所有比他大的左节点连一条容量为1,费用为0的边。因为这两个区间可以同时选而不重叠,所以只需要用一个容量。
因为k要限制,所以我们要限制从源点流出的流量,所以我们建立一个超级源点,将其与源点之间连一条容量为k,费用为0的边。
这样就建好了图,再在这个图上跑一边最大费用最大流,就可以求出解。是一个很妙的建图方式。

代码



#include <bits/stdc++.h>
using namespace std;
const int MAXN = 200000;
const int INF = 1<<30;

int n, k, s1, s2, t;
int tot=1, front[MAXN];
int dis[MAXN], pre[MAXN], flow[MAXN];
int ansflow, anscost;
bool inq[MAXN];

struct tLine
{
    int l, r, size;
    bool operator < (const tLine& rhs) const
    {
        return (l != rhs.l) ? l < rhs.l : r < rhs.r;
    }
} line[MAXN];

struct tEdge
{
    int v, w, next, c, f;
    inline void addEdge(int tmpu, int tmpv, int tmpc, int tmpw)
    {
        v = tmpv; w = tmpw; c = tmpc; f = 0;
        next = front[tmpu]; front[tmpu] = tot;
    }
} e[MAXN*2];

bool spfa()
{
    queue <int> q; bool r = false;
    for(int i=1; i<=n*2+3; i++) dis[i] = -INF;
    memset(inq, false, sizeof(inq));
    memset(flow, 0, sizeof(flow));
    memset(pre, 0, sizeof(pre));
    q.push(s1); dis[s1] = 0; inq[s1] = true; flow[s1] = INF;

    while(!q.empty())
    {
        int u = q.front(); q.pop(); inq[u] = false;
        //printf("u:%d\n", u);

        for(int i=front[u]; i>0; i=e[i].next)
        {
            int v = e[i].v, w = e[i].w, maxflow = e[i].c - e[i].f;

            if(maxflow <= 0) continue;
            if(dis[v] >= dis[u] + w) continue;

            dis[v] = dis[u] + w; pre[v] = i^1;
            flow[v] = min(flow[u], maxflow);

            if(v == t) r = true;
            if(inq[v] == true) continue;
            q.push(v);
            inq[v] = true;
        }
    }

    return r;
}

void kp()
{
    while(spfa() == true)
    {
        int k = t, nowflow = flow[t], nowcost = dis[t];

        while(k != s1)
        {
            e[pre[k]].f -= nowflow;
            e[pre[k]^1].f += nowflow;
            k = e[pre[k]].v;
            //printf("k:%d\n", k);
        }

        ansflow += nowflow;
        anscost += nowflow * nowcost;
    }
}

int main()
{
    scanf("%d%d", &n, &k); //左端点:1~n, 右端点:n+1~2n;
    s1 = 2*n+1; s2 = 2*n+2; t = 2*n+3;
    for(int i=1; i<=n; i++)
    {
        int tmp1, tmp2;
        scanf("%d%d", &tmp1, &tmp2);

        line[i].l = min(tmp1, tmp2);
        line[i].r = max(tmp1, tmp2);
        line[i].size = abs(tmp1 - tmp2);
    }
    sort(line+1, line+1+n);

    //for(int i=1; i<=n; i++) printf("%d ", line[i].l); printf("\n");
    //for(int i=1; i<=n; i++) printf("%d ", line[i].r); printf("\n");

    e[++tot].addEdge(s1, s2, k, 0);
    e[++tot].addEdge(s2, s1, 0, 0);
    for(int i=1; i<=n; i++)
    {
        e[++tot].addEdge(s2, i, 1, 0);
        e[++tot].addEdge(i, s2, 0, 0);
        e[++tot].addEdge(i, i+n, 1, line[i].size);
        e[++tot].addEdge(i+n, i, 0, -line[i].size);
        e[++tot].addEdge(i+n, t, 1, 0);
        e[++tot].addEdge(t, i+n, 0, 0);

        for(int j=i+1; j<=n; j++)
        {
            if(line[j].l < line[i].r) continue;

            e[++tot].addEdge(i+n, j, 1, 0);
            e[++tot].addEdge(j, i+n, 0, 0);
        }
    }

    kp();
    printf("%d\n", anscost);

    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值