[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-midcdot 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
",ans);
}