【BZOJ】4753: [Jsoi2016]最佳团体 01分数规划+树上背包

【题意】n个人,每个人有价值ai和代价bi和一个依赖对象ri<i,选择 i 时 ri 也必须选择(ri=0时不依赖),求选择k个人使得Σai/Σbi最大。n<=2500,ai,bi<=1e4。

【算法】01分数规划+树上背包

【题解】首先二分答案ans,根据01分数规划赋新的权值ci=ai-ans*bi,转化为是否能在树上找k个点使得权值和>=0。

设f[i][j]表示子树 i 选择 j 个点的最大权值和,然后做树上背包即可。

注意:第一维从大到小枚举j,第二维枚举儿子背包。这个背包比较特殊,是因为一批物品只能也必须取一个

两个节点只在它们LCA处计算一次,所以只要背包不枚举满,均摊复杂度就是N^2。

总复杂度O(n^2*log ai)。

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn=2510;
const double inf=-100000000000000;

int first[maxn],n,K,a[maxn],b[maxn],tot,sz[maxn];
double f[maxn][maxn],c[maxn];
struct edge{int v,from;}e[maxn];
void insert(int u,int v){tot++;e[tot].v=v;e[tot].from=first[u];first[u]=tot;}
double max(double a,double b){return a<b?b:a;}
void dfs(int x){
    for(int i=0;i<=K;i++)f[x][i]=inf;
    if(x)f[x][1]=c[x],sz[x]=1;else f[x][0]=0;
    for(int i=first[x];i;i=e[i].from){
        dfs(e[i].v);sz[x]+=sz[e[i].v];
        for(int k=min(K,sz[x]);k>=1;k--)//
            for(int j=min(k-(x!=0),sz[e[i].v]);j>=1;j--)if(f[x][k-j]>inf+10){
                f[x][k]=max(f[x][k],f[x][k-j]+f[e[i].v][j]);
            }else break;
    }
}
bool solve(double ans){
    for(int i=1;i<=n;i++)c[i]=1.0*a[i]-ans*b[i];
    dfs(0);
    return f[0][K]>=0;
}
int main(){
    scanf("%d%d",&K,&n);
    double l=0,r=0,mid;
    for(int i=1;i<=n;i++){
        int fa;
        scanf("%d%d%d",&b[i],&a[i],&fa);
        insert(fa,i);
    }
    r=1e4;//
    while(r-l>1e-4){
        mid=(l+r)/2;
        if(solve(mid))l=mid;else r=mid;
    }
    printf("%.3lf",l);
    return 0;
}
View Code

 

posted @ 2018-03-12 20:20  ONION_CYC  阅读(...)  评论(...编辑  收藏