[JSOI2016]最佳团体(01分数规划+树形背包)

[JSOI2016]最佳团体(01分数规划+树形背包)

题面

JSOI信息学代表队一共有N名候选人,这些候选人从1到N编号。方便起见,JYY的编号是0号。每个候选人都由一位编号比他小的候选人Ri推荐。如果Ri=0则说明这个候选人是JYY自己看上的。为了保证团队的和谐,JYY需要保证,如果招募了候选人i,那么候选人Ri"也一定需要在团队中。当然了,JYY自己总是在团队里的。每一个候选人都有一个战斗值Pi",也有一个招募费用Si"。JYY希望招募K个候选人(JYY自己不算),组成一个性价比最高的团队。也就是,这K个被JYY选择的候选人的总战斗值与总招募总费用的比值最大。
\(n,K \leq 2500\)

分析

01分数规划的套路,先二分答案mid,每个点权值变成\(P_i-mid\cdot S_i\),那么问题就变成了在树上选择一个大小为\(K+1\)的连通块,求最大权值,如果最大权值大于0,就说明答案大于\(mid\).直接套树形背包的板子:

for(int i=0;i<=n;i++){
    for(int j=0;j<=min(i,k+1);j++){
        dp[nex[i]][j]=max(dp[nex[i]][j],dp[i][j]);
        dp[i+1][j+1]=max(dp[i+1][j+1],dp[i][j]+p[hash_dfn[i]]-mid*s[hash_dfn[i]]);
    }
}
return dp[n+1][k+1]>=eps;

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define maxn 3000 
#define eps 1e-5
#define INF 1e18
using namespace std;
typedef  double db;
int n,k;
struct edge{
	int from;
	int to;
	int next;
}E[maxn*2+5];
int head[maxn+5];
int sz=1;
void add_edge(int u,int v){
	sz++;
	E[sz].from=u;
	E[sz].to=v;
	E[sz].next=head[u];
	head[u]=sz; 
}

int tim;
int dfn[maxn+5];
int hash_dfn[maxn+5];
int nex[maxn+5];
int s[maxn+5],p[maxn+5]; 
db dp[maxn+5][maxn+5];
void dfs(int x){
	dfn[x]=tim++;
	hash_dfn[dfn[x]]=x;
	for(int i=head[x];i;i=E[i].next){
		int y=E[i].to;
		dfs(y); 
	}
	nex[dfn[x]]=tim;
}

bool check(db mid){
	for(int i=1;i<=n+1;i++){
		for(int j=0;j<=k+1;j++){
			dp[i][j]=-INF;
		}
	}

	for(int i=0;i<=n;i++){
		for(int j=0;j<=min(i,k+1);j++){
			dp[nex[i]][j]=max(dp[nex[i]][j],dp[i][j]);
			dp[i+1][j+1]=max(dp[i+1][j+1],dp[i][j]+p[hash_dfn[i]]-mid*s[hash_dfn[i]]);
		}
	}
	return dp[n+1][k+1]>=eps; 

}

int main(){
	int f;
	scanf("%d %d",&k,&n);	
	for(int i=1;i<=n;i++){
		scanf("%d %d %d",&s[i],&p[i],&f);
		add_edge(f,i);
	}
	dfs(0);
	db l=0,r=1e4;
	db mid;
	db ans=0;
	while(r-l>=eps){
		mid=(l+r)/2;
		if(check(mid)){
			ans=mid;
			l=mid+eps;
		}else r=mid-eps; 
	}
	printf("%.3lf\n",ans);
} 

猜你喜欢

转载自www.cnblogs.com/birchtree/p/12926107.html