[bzoj4753][Jsoi2016]最佳团体【0/1分数规划】【dp】
【题目描述】
Description
JSOI信息学代表队一共有N名候选人,这些候选人从1到N编号。方便起见,JYY的编号是0号。每个候选人都由一位
编号比他小的候选人Ri推荐。如果Ri=0则说明这个候选人是JYY自己看上的。为了保证团队的和谐,JYY需要保证,
如果招募了候选人i,那么候选人Ri"也一定需要在团队中。当然了,JYY自己总是在团队里的。每一个候选人都有
一个战斗值Pi",也有一个招募费用Si"。JYY希望招募K个候选人(JYY自己不算),组成一个性价比最高的团队。
也就是,这K个被JYY选择的候选人的总战斗值与总招募总费用的比值最大。
Input
输入一行包含两个正整数K和N。
接下来N行,其中第i行包含3个整数Si,Pi,Ri表示候选人i的招募费用,战斗值和推荐人编号。
对于100%的数据满足1≤K≤N≤2500,0<"Si,Pi"≤10^4,0≤Ri<i
Output
输出一行一个实数,表示最佳比值。答案保留三位小数。
Sample Input
1 2
1000 1 0
1 1000 1
1000 1 0
1 1000 1
Sample Output
0.001
HINT
2017.9.12新加数据一组 By GXZlegend
Source
【题解】 题目大意是一棵树,每个点有价值和花费,要选择一个包含根节点的连通块,使 价值和A/花费和B 最大。
0/1分数规划套路题,变式得 A=ans*B,显然可以二分ans,将每个的权值设为a-ans*b,再在新树上判断是否有可行的连通块使权值和 >0,dp一下就行了
若存在,则说明当前二分的ans可以被满足,否则不行。
/* -------------- user Vanisher problem bzoj-4753 ----------------*/ # include <bits/stdc++.h> # define ll long long # define inf 1e9 # define eps 1e-6 # define N 3010 using namespace std; int read(){ int tmp=0, fh=1; char ch=getchar(); while (ch<'0'||ch>'9'){if (ch=='-') fh=-1; ch=getchar();} while (ch>='0'&&ch<='9'){tmp=tmp*10+ch-'0'; ch=getchar();} return tmp*fh; } struct node{ int data,next; }e[N]; int place,head[N],size[N],k,n,fa[N]; double a[N],b[N],f[N][N],g[N]; void build(int u, int v){ e[++place].data=v; e[place].next=head[u]; head[u]=place; } void dp(int x, double p){ size[x]=1; f[x][0]=0, f[x][1]=a[x]-p*b[x]; for (int ed=head[x]; ed!=0; ed=e[ed].next){ dp(e[ed].data,p); for (int i=0; i<=size[x]+size[e[ed].data]; i++) g[i]=-inf; g[0]=0; for (int i=1; i<=size[x]; i++) for (int j=0; j<=size[e[ed].data]; j++) g[i+j]=max(g[i+j],f[e[ed].data][j]+f[x][i]); size[x]=size[x]+size[e[ed].data]; for (int i=0; i<=size[x]; i++) f[x][i]=g[i]; } } double check(double p){ dp(1,p); return f[1][k]; } int main(){ k=read()+1, n=read()+1; for (int i=2; i<=n; i++){ b[i]=read(), a[i]=read(), fa[i]=read()+1; build(fa[i],i); } double pl=0, pr=2e4, ans=0; while (pl+eps<=pr){ double mid=(pl+pr)/2; if (check(mid)>=0) ans=mid, pl=mid+eps; else pr=mid-eps; } printf("%.3lf\n",ans); return 0; }