1. 程式人生 > >解題報告 之 POJ3680 Intervals

解題報告 之 POJ3680 Intervals

Description

You are given N weighted open intervals. The ith interval covers (aibi) 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 aibiwi(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,但是會錯解為13。

所以我們應該怎麼辦呢?這裡有一個很奇妙的思路。將區間端點處理過後,將每個端點作為一個節點,依次連線,負載為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;
}

五一第二天,大家都出去玩啦,都不看我的博文,桑心,發個神煩狗給大家助興。

最大流告一段落啦。