【JSOI2016】最佳团队

题目

【JSOI2016】最佳团队_第1张图片

输入

【JSOI2016】最佳团队_第2张图片
1 2
1000 1 0
1 1000 1

输出

这里写图片描述
0.001

数据范围

这里写图片描述


剖解题目

一棵以0为根的树,除了根以外,每一个节点有一个价值与一个花费。若选择一个节点则其父亲也必须选,问如何选择使得总价值与总花费的比值最大。


思路

开头,一眼dp,有点像背包?
结尾,一眼01规划。。。。
发现是棵树,可以在树上背包!
然而发现自己背包并不熟,01规划也从未打过。。。。
然后就想到dfn序,弄下来搞了搞。
结果因为一个傻逼错误调了4个钟。。。。


解法

首先我们二分答案mid。
PiSi>=mid 可化为 PimidSi>=0
那么一个点的性价比为val[i]=p[i]-mid*s[i]。、

1.树上依赖背包dp,寻找最大的联通块的权值。
2.将dfn序弄下来:
设f[i,j]表示序列中前i个点选了j个点所能得到的最大性价比。
转移有两种:
若不选,则转移到f[i+size[dfn[i]]][j]上。
若选,则转移到f[i+1][j+1]上。

时间O(n^2 log n)


代码

#include
#include
#include
#include
#include
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define dou double

using namespace std;

const int maxn=2505;
const dou ep=1e-5;
int next[maxn*2],fre[maxn],go[maxn*2],dad[maxn],dfn[maxn],size[maxn],pos[maxn];
dou s[maxn],p[maxn],f[maxn][maxn],val[maxn],mid;
int n,m,num,id;

void add(int x,int y)
{
    go[++num]=y;
    next[num]=fre[x];
    fre[x]=num;
}
void dfs(int x)
{
    dfn[++id]=x; size[x]=1; pos[x]=id;
    int i=fre[x];
    while (i){
        int u=go[i];
        dfs(u);
        size[x]+=size[u];
        i=next[i];
    }
}
void dp(dou mid)
{
    fo(i,1,n) val[pos[i]]=p[i]-mid*s[i];
    fo(i,1,n+1)
        fo(j,0,m+1) f[i][j]=-1e18;
    fo(i,0,n){
        fo(j,0,min(i,m+1)){
            if (f[i][j]+val[i]>f[i+1][j+1]) {
                f[i+1][j+1]=f[i][j]+val[i];
            }
            if (f[i][j]>f[i+size[dfn[i]]][j]){
                f[i+size[dfn[i]]][j]=f[i][j];
            }

        }
    }
}
int main()
{
    //freopen("T.in","r",stdin);
    scanf("%d%d",&m,&n);
    fo(i,1,n){
        scanf("%lf%lf%d",&s[i],&p[i],&dad[i]);
        add(dad[i],i);
    }
    id=-1;
    dfs(0);
    dou l=0,r=1e4;
    while (r-l>ep){
        mid=(l+r)/2;
        dp(mid);
        if (f[n+1][m+1]>=0) l=mid;
        else r=mid;
    }
    printf("%.3lf\n",mid);
}

【JSOI2016】最佳团队_第3张图片

你可能感兴趣的:(信息技术,动态规划,分治,01规划)