1. 程式人生 > >【bzoj4753】[Jsoi2016]最佳團體 分數規劃+樹形背包dp

【bzoj4753】[Jsoi2016]最佳團體 分數規劃+樹形背包dp

固定 題目 時間復雜度 scanf clas bzoj true ++ 策略

題目描述

JSOI信息學代表隊一共有N名候選人,這些候選人從1到N編號。方便起見,JYY的編號是0號。每個候選人都由一位編號比他小的候選人Ri推薦。如果Ri=0則說明這個候選人是JYY自己看上的。為了保證團隊的和諧,JYY需要保證,如果招募了候選人i,那麽候選人Ri"也一定需要在團隊中。當然了,JYY自己總是在團隊裏的。每一個候選人都有一個戰鬥值Pi",也有一個招募費用Si"。JYY希望招募K個候選人(JYY自己不算),組成一個性價比最高的團隊。也就是,這K個被JYY選擇的候選人的總戰鬥值與總招募總費用的比值最大。

輸入

輸入一行包含兩個正整數K和N。 接下來N行,其中第i行包含3個整數Si,Pi,Ri表示候選人i的招募費用,戰鬥值和推薦人編號。 對於100%的數據滿足1≤K≤N≤2500,0<"Si,Pi"≤10^4,0≤Ri<i

輸出

輸出一行一個實數,表示最佳比值。答案保留三位小數。

樣例輸入

1 2
1000 1 0
1 1000 1

樣例輸出

0.001


題解

分數規劃+樹形背包dp

二分答案mid,題目便轉化為求是否存在滿足題目條件的集合V,使得$\frac{\sum\limits_{i\in V}p_i}{\sum\limits_{i\in V}s_i}\ge mid$,即$\sum\limits_{i\in V}(s_i-mid·p_i)\ge 0$。

這就轉化為了一個樹形dp問題。

令a[i]=s[i]-mid*p[i],表示i的性價比。設f[i][j]表示從子樹i中選出j個且選i,可以獲得的最大性價比之和,顯然f[i][1]=a[i]。

那麽對於每個i的子節點son,相當於有體積為1~si[son]共si[son]個物品放入背包內,每個物品可以放或不放。這相當於01背包問題。

但是這樣dp的時間復雜度好像是$O(n^3)$的。

事實上,這裏面的有效狀態是很少的,如果只枚舉有效狀態,dp的時間復雜度將到達可以接受的$O(n^2)$。

具體粗略證明:

更新一棵子樹的時間復雜度=更新該節點的子節點的時間復雜度+計算該節點的時間復雜度。

計算該節點的復雜度,如果采用最優策略,使用嚴格的有效區間範圍來進行dp,時間復雜度應該為

$O(\sum\limits_{i=1}^m(1+\sum\limits_{j=1}^{i-1}si_j)·si_i)=O(\sum\limits_{i=1}^m\sum\limits_{j=1}^{i-1}si_j·si_i+\sum\limits_{i=1}^msi_i)=O((\sum\limits_{i=1}^msi_i)^2-\sum\limits_{i=1}^msi_i^2+\sum\limits_{i=1}^msi_i)=O(si_x^2-\sum\limits_{i=1}^msi_i^2+si_x)$,

其中$si_i(i\in[1,m])$表示x的第i個兒子節點的子樹大小(總共有m個兒子節點),$si_x$表示x的子樹大小。

而葉子節點的時間復雜度是$O(1)$的,進而我們可以使用累加法計算出總體dp的時間復雜度為$O(si_{root}^2+\sum\limits_{i=1}^nsi_i^2)=O(n^2)$。

因此總的時間復雜度是$O(n^2\log n)$。

為了避免精度誤差帶來的答案錯誤,建議固定二分c次,c值視情況而定,本題中取30可過。

另外由於數據太水了,所以$O(n^3\log n)$的做法也是可以通過本題的。(其實可以自己做一個鏈的數據卡掉它)

#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 2510
using namespace std;
int head[N] , to[N] , next[N] , cnt , si[N] , w[N] , v[N] , n;
double a[N] , mid , f[N][N];
void add(int x , int y)
{
	to[++cnt] = y , next[cnt] = head[x] , head[x] = cnt;
}
void init(int x)
{
	int i;
	si[x] = 1;
	for(i = head[x] ; i ; i = next[i]) init(to[i]) , si[x] += si[to[i]];
}
void dfs(int x)
{
	int i , j , k , tot = 0 , b = 0;
	memset(f[x] , 0xc2 , sizeof(f[x]));
	if(x) f[x][1] = a[x] , tot ++ , b ++ ;
	else f[x][0] = 0;
	for(i = head[x] ; i ; i = next[i])
	{
		dfs(to[i]);
		for(j = tot ; j >= b ; j -- )
			for(k = 1 ; k <= si[to[i]] ; k ++ )
				f[x][j + k] = max(f[x][j + k] , f[x][j] + f[to[i]][k]);
		tot += si[to[i]];
	}
}
int main()
{
	int n , k , i , x , c = 30;
	double l = 0 , r = 0;
	scanf("%d%d" , &k , &n);
	for(i = 1 ; i <= n ; i ++ ) scanf("%d%d%d" , &w[i] , &v[i] , &x) , add(x , i) , r = max(r , (double)v[i]);
	init(0);
	while(c -- )
	{
		mid = (l + r) / 2;
		for(i = 1 ; i <= n ; i ++ ) a[i] = v[i] - mid * w[i];
		dfs(0);
		if(f[0][k] >= 0) l = mid;
		else r = mid;
	}
	printf("%.3lf\n" , (l + r) / 2);
	return 0;
}

【bzoj4753】[Jsoi2016]最佳團體 分數規劃+樹形背包dp