【JSOI2016】最佳团体(树型dp)

题目描述

JSOI 信息学代表队一共有 NN 名候选人,这些候选人从 11 到 NN 编号。方便起见,JYY 的编号是 00 号。每个候选人都由一位编号比他小的候选人 R_iRi​ 推荐。如果 R_i = 0Ri​=0 ​,则说明这个候选人是 JYY 自己看上的。

为了保证团队的和谐,JYY 需要保证,如果招募了候选人 ii ,那么候选人 R_iRi​ 也一定需要在团队中。当然了,JYY 自己总是在团队里的。每一个候选人都有一个战斗值 P_iPi​ ,也有一个招募费用 S_iSi​ 。JYY 希望招募 KK 个候选人(JYY 自己不算),组成一个性价比最高的团队。也就是,这 KK 个被 JYY 选择的候选人的总战斗值与总招募费用的比值最大。

 

输入输出格式

输入格式:

输入一行包含两个正整数 KK 和 NN 。

接下来 NN 行,其中第 ii 行包含三个整数 S_i , P_i, R_i, 表示候选人 ii 的招募费用,战斗值和推荐人编号。

 

输出格式:

输出一行一个实数,表示最佳比值。答案保留三位小数。

这题是道经典题了,值得记一记(虽然自己并不会做)。我从洛谷大神那里学到了一种新方法。洛谷题解

稍加思考

树?直接设一个f(i,j)表示以i为根节点,选了j个子节点时的最大比值。然而这样上来就推不走了。容易联想到0/1分数规划。可以知道:ans=\sum_{i=1}^{K}P_i\ \ \div \ \ \sum_{i=1}^{K}。因为ans肯定大于0,所以展开移项有:ans=(P_1+P_2+...+P_K)\ \div\ (S_1+S_2+...+S_K)>0\ \ ;\ \ \sum_{i=1}^{K}P_i-S_i*ans\ >\ 0。于是加上一个二分ans,每次做dp,看最终的最优解是否符合要求即可。

新的方法

习惯性地做之前看了一眼题解的文字说明,结果发现了新大陆。有大神说是可以把它转化为DFS序,直接做一个背包dp,于是开始尝试自己搞,搞到最后还是把题解抄袭学习完了....

用dfn[i]记录先序遍历节点i时的顺序,用las[i]记录i这棵子树遍历完之后,下一棵子树根节点的遍历顺序。然后直接1~N做一个0/1背包dp即可。注意此处根节点的顺序在子节点之前,且是根选了才可选儿子,所以向后更新。此外,由于树与树是独立的,所以当前树更新时,要更新紧接着它的下一棵树。实在太水怎么也写不对,所以最终还是按着题解写了.......

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>

using namespace std;

typedef long long LL;
typedef double DB;

const int MAXN=2520;
const DB eps=0.0001;
const DB INF=500000000;

int N,K,np=0,clock=0;
int last[MAXN];
int dfn[MAXN];
int las[MAXN];
DB S[MAXN];
DB P[MAXN];
DB d[MAXN];
DB f[MAXN][MAXN];

struct edge{int to,pre;}E[MAXN*2];

void addedge(int u,int v)
{
	E[++np]=(edge){v,last[u]};
	last[u]=np;
}

void DFS(int i)
{
	dfn[i]=clock++;  //遍历顺序 
	for(int p=last[i];p;p=E[p].pre)
	{
		int j=E[p].to;
		DFS(j);
	}
	las[dfn[i]]=clock;  //注意上面是先赋值再+1,所以las指向下一棵子树 
}


DB check(DB mid)
{
	int i,j;
	
	for(i=0;i<=N;i++)   //0/1分数规划 
		d[dfn[i]]=P[i]-mid*S[i];
	
	for(i=1;i<=N+1;i++)    //初始化 
	for(j=0;j<=K+1;j++)
		f[i][j]=-INF;
		
	for(i=0;i<=N;i++)    //就是一个背包问题 
	for(j=0;j<=min(i,K+1);j++)
	{
		f[i+1][j+1]=max(f[i+1][j+1],f[i][j]+d[i]);
		f[las[i]][j]=max(f[las[i]][j],f[i][j]);
	}
	
	return f[N+1][K+1];
}

int main()
{
	cin>>K>>N;
	
	int i,u;
	DB w,MAXP=0;
	
	for(i=1;i<=N;i++)
	{
		cin>>S[i]>>P[i]>>u;
		addedge(u,i);
		MAXP=max(MAXP,P[i]);
	}
	
	DFS(0);   //形成DFS序 
	
	DB L=0,R=MAXP,MID;
	while(R-L>eps)
	{
		MID=(L+R)/2.0;
		if(check(MID)>=eps) L=MID;
		else R=MID;
	}
	
	printf("%.3lf",L);
	
	return 0;
}

猜你喜欢

转载自blog.csdn.net/WWWengine/article/details/81286125