1. 程式人生 > >(轉)HDU2639 01揹包 第K優決策

(轉)HDU2639 01揹包 第K優決策

求第K優解

對於求次優解、第K優解類的問題,如果相應的最優解問題能寫出狀態轉移方程、用動態規劃解決,那麼求次優解往往可以相同的複雜度解決,第K優解則比求最優解的複雜度上多一個係數K。其基本思想是將每個狀態都表示成有序佇列,將狀態轉移方程中的max/min轉化成有序佇列的合併。這裡仍然以01揹包為例講解一下。首先看01揹包求最優解的狀態轉移方程:f[i][v]=max{f[i-1][v],f[i-1][v-c[i]]+w[i]}。如果要求第K優解,那麼狀態f[i][v]就應該是一個大小為K的陣列f[i][v][1..K]。其中f[i][v][k]表示前i個物品、揹包大小為 v時,第k優解的值。“f[i][v]是一個大小為K的陣列”這一句,熟悉C語言的同學可能比較好理解,或者也可以簡單地理解為在原來的方程中加了一維。 顯然f[i][v][1..K]這K個數是由大到小排列的,所以我們把它認為是一個有序佇列。然後原方程就可以解釋為:f[i][v]這個有序佇列是由f[i-1][v]和f[i-1][v-c[i]]+w[i]這兩個有序佇列合併得到的。有序佇列f[i-1][v]即f[i-1][v][1..K],f[i-1][v-c[i]]+w[i]則理解為在f[i-1][v-c[i]] [1..K]的每個數上加上w[i]後得到的有序佇列。合併這兩個有序佇列並將結果的前K項儲存到f[i][v][1..K]中的複雜度是O(K)。最後的答案是f[N][V][K]。總的複雜度是O(VNK)。

為什麼這個方法正確呢?實際上,一個正確的狀態轉移方程的求解過程遍歷了所有可用的策略,也就覆蓋了問題的所有方案。只不過由於是求最優解,所以其 它在任何一個策略上達不到最優的方案都被忽略了。如果把每個狀態表示成一個大小為K的陣列,並在這個陣列中有序的儲存該狀態可取到的前K個最優值。那麼, 對於任兩個狀態的max運算等價於兩個由大到小的有序佇列的合併。另外還要注意題目對於“第K優解”的定義,將策略不同但權值相同的兩個方案是看作同一個解還是不同的解。如果是前者,則維護有序佇列時要保證佇列裡的數沒有重複的。

用個形象的比喻吧:如果我想知道學年最高分,那麼,我只要知道每個班級的最高分,然後統計一遍就可以了。如果我想知道學年前十呢?我必須要知道每個班的前十名。大家在心裡模擬一下,對,這就是本題核心的演算法。兩種決策,就可以看作這個學年只有兩個班。

AC程式碼:

#include<iostream>
using namespace std;
 
int n,vol,k;
int dp[1005][35],value[105],cost[105],A[35],B[35];
 
void kth_ZeroOnePack()
{
	memset(dp,0,sizeof(dp));
 
	int i,j,kk,a,b,c;
	for(i=1;i<=n;++i)
	{
		for(j=vol;j>=cost[i];--j)
		{
			for(kk=1;kk<=k;++kk)
			{
				A[kk]=dp[j-cost[i]][kk]+value[i];
				B[kk]=dp[j][kk];
			}
 
			A[kk]=B[kk]=-1;
			a=b=c=1;
			while(c<=k && (A[a]!=-1 || B[b]!=-1))
			{
				if(A[a]>B[b])
					dp[j][c]=A[a],++a;
				else
					dp[j][c]=B[b],++b;
 
				if(dp[j][c]!=dp[j][c-1])
					++c;
			}
		}
	}
 
	cout<<dp[vol][k]<<endl;
}
		
int main()
{
	int tCase;
 
	cin>>tCase;
	while(tCase--)
	{
		cin>>n>>vol>>k;
 
		for(int i=1;i<=n;++i)
			cin>>value[i];
		for(int j=1;j<=n;++j)
			cin>>cost[j];
 
		kth_ZeroOnePack();
	}
 
	return 0;
}