JSOI2016 最佳团体

题目链接:戳我

首先,你要知道这道题是一棵树!
然后,就是从树上选择K个点,让他们的权值和最大,其中如果选择了一个点,那么它的父亲一定要选。
有依赖的背包问题。
\(dp[i][j]\)表示在以i为根的子树中,选择j个点的最大权值。
但是直接在树上做应该是\(O(n^2k)\)的,所以我们考虑优化——
就是我们考虑在树上将背包合并,这样子每个子树都只会被合并一次,这样时间复杂度就降为了\(O(nk)\)
然后就能过了。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define MAXN 3010
#define eps 1e-6
using namespace std;
int n,m,t,tot;
int head[MAXN],siz[MAXN],fa[MAXN];
double dp[MAXN][MAXN],now[MAXN],tmp[MAXN],w[MAXN],v[MAXN];
struct Edge{int nxt,to;}edge[MAXN<<1];
inline void add(int from,int to){edge[++t].nxt=head[from],edge[t].to=to,head[from]=t;}
inline void merge(int x,int y)
{
    for(int i=0;i<=m+1;i++) tmp[i]=-1e9;
    for(int i=1;i<=siz[x];i++)
        for(int j=1;j<=siz[y];j++)
            tmp[i+j]=max(tmp[i+j],dp[x][i]+dp[y][j]);
    for(int i=0;i<=m+1;i++) dp[x][i]=max(dp[x][i],tmp[i]);
}
inline void solve(int x)
{
    dp[x][1]=now[x];
    siz[x]=1;
    for(int i=head[x];i;i=edge[i].nxt)
    {
        int v=edge[i].to;
        solve(v);
        merge(x,v);
        siz[x]+=siz[v];
    }
}
inline bool check(double x)
{
    for(int i=0;i<=n;i++)
        for(int j=0;j<=m+1;j++)
            dp[i][j]=-1e9;
    memset(siz,0,sizeof(siz));
    for(int i=1;i<=n;i++) now[i]=v[i]-w[i]*x;
    solve(0);
    if(dp[0][m+1]>=0) return true;
    else return false;
}
int main()
{
    #ifndef ONLINE_JUDGE
    freopen("ce.in","r",stdin); 
    #endif
    scanf("%d%d",&m,&n);
    double l=1e9,r=-1e9;
    for(int i=1;i<=n;i++)
    {
        scanf("%lf%lf%d",&w[i],&v[i],&fa[i]);
        add(fa[i],i);
        l=min(l,v[i]/w[i]);
        r=max(r,v[i]/w[i]);
    }
    
    while(l+eps<r)
    {
        double mid=(l+r)/2;
        if(check(mid)) l=mid;
        else r=mid;
    }
    printf("%.3lf\n",l);
    return 0;
}

posted @ 2019-05-22 23:33  风浔凌  阅读(150)  评论(0编辑  收藏  举报