标签:poj3680 intervals 最小费用流 标号法 负权边
Description
You are given N weighted open intervals. The ith interval covers (ai, bi) and weighs wi. Your task is to pick some of the intervals to maximize the total weights under the limit that no point in the real axis is covered more than k times.
Input
The first line of input is the number of test case.
The first line of each test case contains two integers, N and K (1 ≤ K ≤ N ≤ 200).
The next N line each contain three integers ai, bi, wi(1 ≤ ai < bi ≤ 100,000, 1 ≤ wi ≤ 100,000) describing the intervals.
There is a blank line before each test case.
Output
For each test case output the maximum total weights in a separate line.
Sample Input
4 3 1 1 2 2 2 3 4 3 4 8 3 1 1 3 2 2 3 4 3 4 8 3 1 1 100000 100000 1 2 3 100 200 300 3 2 1 100000 100000 1 150 301 100 200 300
Sample Output
14 12 100000 100301
题目大意:给你n个区间(ai,bi),对应区间的权重为ci。现在让你挑选一些区间,使得任一点出现的次数不超过k,问你满足条件的方案的最大权重和为多少?
分析:作为《挑战》的章末题,还是很有难度的。首先讲了一下k=1时的办法,此时即是选一些不重叠的区间使得他们的权重和最大,那么可以用dp的方法求解。首先把区间的点从左到右排序并去除重复点。然后用dp[i]表示只考虑到第i个点之前,能够达到的最大权重和。
则dp[i]=max{dp[i-1] , dp[j]+w[k] | 要求区间k的ak为第j个点,bk为第i个点},即去看选不选某区间来更新最大值。这个问题称为区间图的最大权独立集问题(好高端的样子。。。/kb)
最朴素的想法是重复DP K次来求,但是这样是错误的,比如下图这种情形。若K=2,答案应该是16,但是会错解为9。
所以我们应该怎么办呢?这里有一个很奇妙的思路。将区间端点处理过后,将每个端点作为一个节点,依次连接,负载为INF,费用为0。(别急,我们先把图建好再解释为什么);对与每个给定的区间(a,b),我们将a,b端点对应的节点相连,负载为1,费用-c;超级源点连节点1,负载为k,费用为0;最后一个端点对应的节点连超级汇点,负载为k,费用为0。
图算是建好了,那么为什么这么建呢?对于每个区间我们都把边的负载设为-c。这样我们就可以巧妙地把问题转化为跑源点到汇点的最小费用流,此时最大权重= - 最小非要用流(注意是负的)。换句话说,我们将权重转化为了负权边,再求类似k条最短路的意味。那么我们是怎么限制一个点不被访问超过k次呢?因为我们注意到图中的节点连接都是一路向右的,所以这k的流量经过所有节点最多1次,所以不会有节点被访问超过k次。
此处还要用到一个技巧,就是当图中有负权边的时候,我们处理一下,有两种办法。有一种经济又实惠的方法就是标号法,设置一个标号数组h[MAXN],将负权变为非负。(参考代码1,这种方法时间代价比较小) 第二中方法就是假设满流法,对所有负权边,假设满流,如负权边(2,3)-5。则建图的时候3向2连边,负载为5。然后src连负权边入点,而负权边出点连des。注意这个方法的最小费用流=所有负权边和+处理后最小费用流,且最小费用流的流量为f+负权边流量,感觉很麻烦。(参考代码2,详解如图,出自《挑战》)
上代码:
//标号法,设立一个标号数组h[MAXN] #include<iostream> #include<cstring> #include<algorithm> #include<deque> #include<cstdio> #include<vector> using namespace std; const int MAXN = 510; const int MAXM = 51010; const int INF = 0x3f3f3f3f; struct Edge { int from, to, cap, next, cost; }; Edge edge[MAXM]; int head[MAXN]; int h[MAXN]; int preve[MAXN]; int prevv[MAXN]; int dist[MAXN]; int a[MAXN], b[MAXN], c[MAXN]; int src, des, cnt; void addedge( int from, int to, int cap, int cost ) { edge[cnt].from = from; edge[cnt].to = to; edge[cnt].cap = cap; edge[cnt].cost = cost; edge[cnt].next = head[from]; head[from] = cnt++; swap( from, to ); edge[cnt].from = from; edge[cnt].to = to; edge[cnt].cap = 0; edge[cnt].cost = -cost; edge[cnt].next = head[from]; head[from] = cnt++; } int SPFA() { deque<int> dq; int inqueue[MAXN]; memset( dist, INF, sizeof dist ); memset( inqueue, 0, sizeof inqueue ); inqueue[src] = 1; dist[src] = 0; dq.push_back( src ); while(!dq.empty()) { int u = dq.front(); dq.pop_front(); inqueue[u] = 0; for(int i = head[u]; i != -1; i = edge[i].next) { int v = edge[i].to; if(edge[i].cap&&dist[v] > dist[u] + edge[i].cost + h[u] - h[v]) { dist[v] = dist[u] + edge[i].cost + h[u] - h[v]; prevv[v] = u; preve[v] = i; if(!inqueue[v]) { if(!dq.empty() && dist[v] <= dist[dq.front()]) dq.push_front( v ); else dq.push_back( v ); } } } } return 0; } int min_cost_flow(int f) { memset( h, 0, sizeof h ); int cost=0; while(f > 0) { SPFA(); if(dist[des] == INF) return -1; for(int i = 0; i < MAXN; i++) h[i] += dist[i]; int d = f; for(int i = des; i != src; i = prevv[i]) { d = min( d, edge[preve[i]].cap ); } f -= d; cost += d*h[des]; for(int i = des; i != src; i = prevv[i]) { edge[preve[i]].cap -= d; edge[preve[i] ^ 1].cap += d; } } return cost; } int main() { int n, k; src = 0; des = 505; int kase; cin >> kase; while(kase--) { memset( head, -1, sizeof head ); cnt = 0; cin >> n >> k; vector<int> x; for(int i = 1; i <= n; i++) { cin >> a[i] >> b[i]>>c[i]; x.push_back( a[i] ); x.push_back( b[i] ); } sort( x.begin(), x.end() ); x.erase( unique( x.begin(), x.end() ), x.end() ); //unique将重复元素放到最后并返回第一个重复元素的iterator int m = x.size(); int res = 0; addedge( src, 1, k, 0 ); addedge( m, des, k, 0 ); for(int i = 1; i < m; i++) { addedge( i, i + 1, INF, 0 ); } for(int i = 1; i <= n; i++) { int u = find( x.begin(), x.end(), a[i] ) - x.begin(); int v = find( x.begin(), x.end(), b[i] ) - x.begin(); u++; v++; addedge( u, v, 1, -c[i] ); //addedge( src, v, 1, 0 ); //addedge( u, des, 1, 0 ); //res += (-c[i]); } res = min_cost_flow( k ); //res += min_cost_flow( k ); cout << -res << endl; } return 0; }
然后是另一种解法。
感觉比较神秘。
//假设满流法 #include<iostream> #include<cstring> #include<algorithm> #include<deque> #include<cstdio> #include<vector> using namespace std; const int MAXN = 510; const int MAXM = 51010; const int INF = 0x3f3f3f3f; struct Edge { int from, to, cap, next, cost; }; Edge edge[MAXM]; int head[MAXN]; int preve[MAXN]; int prevv[MAXN]; int dist[MAXN]; int a[MAXN], b[MAXN], c[MAXN]; int src, des, cnt; void addedge( int from, int to, int cap, int cost ) { edge[cnt].from = from; edge[cnt].to = to; edge[cnt].cap = cap; edge[cnt].cost = cost; edge[cnt].next = head[from]; head[from] = cnt++; swap( from, to ); edge[cnt].from = from; edge[cnt].to = to; edge[cnt].cap = 0; edge[cnt].cost = -cost; edge[cnt].next = head[from]; head[from] = cnt++; } int SPFA() { deque<int> dq; int inqueue[MAXN]; memset( dist, INF, sizeof dist ); memset( inqueue, 0, sizeof inqueue ); inqueue[src] = 1; dist[src] = 0; dq.push_back( src ); while(!dq.empty()) { int u = dq.front(); dq.pop_front(); inqueue[u] = 0; for(int i = head[u]; i != -1; i = edge[i].next) { int v = edge[i].to; if(edge[i].cap&&dist[v] > dist[u] + edge[i].cost) //注意差别 { dist[v] = dist[u] + edge[i].cost; prevv[v] = u; preve[v] = i; if(!inqueue[v]) { if(!dq.empty() && dist[v] <= dist[dq.front()]) dq.push_front( v ); else dq.push_back( v ); } } } } return 0; } int min_cost_flow(int f) { int cost=0; while(f > 0) { SPFA(); if(dist[des] == INF) return -1; int d = f; for(int i = des; i != src; i = prevv[i]) { d = min( d, edge[preve[i]].cap ); } f -= d; cost += d*dist[des]; //注意差别 for(int i = des; i != src; i = prevv[i]) { edge[preve[i]].cap -= d; edge[preve[i] ^ 1].cap += d; } } return cost; } int main() { int n, k; src = 0; des = 505; int kase; cin >> kase; while(kase--) { memset( head, -1, sizeof head ); cnt = 0; cin >> n >> k; vector<int> x; for(int i = 1; i <= n; i++) { cin >> a[i] >> b[i]>>c[i]; x.push_back( a[i] ); x.push_back( b[i] ); } sort( x.begin(), x.end() ); x.erase( unique( x.begin(), x.end() ), x.end() ); //unique将重复元素放到最后并返回第一个重复元素的iterator int m = x.size(); int res = 0; addedge( src, 1, k, 0 ); addedge( m, des, k, 0 ); for(int i = 1; i < m; i++) { addedge( i, i + 1, INF, 0 ); } for(int i = 1; i <= n; i++) { int u = find( x.begin(), x.end(), a[i] ) - x.begin(); int v = find( x.begin(), x.end(), b[i] ) - x.begin(); u++; v++; //addedge( u, v, 1, -c[i] ); addedge( v, u, 1, c[i] ); //注意差别 addedge( src, v, 1, 0 ); //注意差别 addedge( u, des, 1, 0 ); //注意差别 res += (-c[i]); //注意差别 } //res = min_cost_flow( k ); res += min_cost_flow( k+n ); //注意差别 cout << -res << endl; } return 0; }
最大流告一段落啦。
标签:poj3680 intervals 最小费用流 标号法 负权边
原文地址:http://blog.csdn.net/maxichu/article/details/45438035