题目描述
给定实直线 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;
}