机房测试7:gift (树形背包)

Posted mowanying

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了机房测试7:gift (树形背包)相关的知识,希望对你有一定的参考价值。

题目:

技术图片

技术图片

 

 

 

技术图片

 

 分析:

分数形式的套路是0/1分数规划:二分一个答案,将式子化简,转换为判断能否选出部分点使得  sigma(b[i]-c[i]*mid)>=0

在这道题中,因为有了依赖关系,而依赖关系不会成环,将依赖关系连边,会成为一棵树,只需要在这颗树上跑树形背包即可。

树形背包:对于一个点v,如果要选v必须选u,就将v的父亲定义成u,每一次要选子树中的点时,强制选根。

弱化版的01分数+树形背包:P4322 [JSOI2016]最佳团体

技术图片
//洛谷P4322 
#include<bits/stdc++.h>
using namespace std;
#define ri register int
#define N 2505
#define eps 1e-5
int to[N],head[N],nex[N],tot=0,n,m,siz[N];
double b[N],c[N],dp[N][N],f[N][N],w[N];//
int read()
{
    int x=0,fl=1; char ch=getchar();
    while(ch<0||ch>9) { if(ch==-) fl=-1; ch=getchar(); }
    while(ch<=9&&ch>=0) x=x*10+ch-0,ch=getchar();
    return x*fl;
}
void add(int a,int b) { to[++tot]=b; nex[tot]=head[a]; head[a]=tot;}
/**/void dfs(int u)
{
    siz[u]=1;
    for(ri i=head[u];i;i=nex[i]){
        int v=to[i];
        dfs(v);
        for(ri j=siz[u];j>=1;--j)
         for(ri k=0;k<=siz[v];++k){//k!=j
             dp[u][j+k]=max(dp[u][j+k],dp[u][j]+dp[v][k]);
             //dp[u][j]=max(dp[u][j],dp[u][j-k]+dp[v][k]);
        }
        siz[u]+=siz[v];
    }
}
bool check(double x)
{
    memset(dp,-10,sizeof(dp));
    for(ri i=0;i<=n;++i) dp[i][1]=b[i]-c[i]*x,dp[i][0]=0;
    dfs(0);
    if(dp[0][m+1]>eps) return true;
    return false;
}
int main()
{
    m=read(); n=read();
    int d;
    for(ri i=1;i<=n;++i) scanf("%lf%lf%d",&c[i],&b[i],&d),add(d,i);
    double l=0,r=1e4;
    while(l+eps<r){
        double mid=(l+r)/2.0;
        if(check(mid)) l=mid+eps;
        else r=mid;
    }
    printf("%.3lf
",l);
}
/*
1 2
1000 1 0
1 1000 1
*/
dfs版

 

那道题中,定义dp[i][j]为i号节点,恰好选j个物品的最大值。

而在这道题中,因为多了a[i]的限制,且至少满足sigma(a[i])>=m,所以要定义dp[i][j]为i号节点,选择大于等于j的a值的最大贡献。

dfs版的时间复杂度很大,所以可以用dfs序转换成序列上的背包转移。

注意:

1. 0在里面是必选点,dfs序应该从0开始,而不是从1开始!!

2. i+1代表一个点的子树内不包括它的节点编号,也可以代表另一个子树的节点编号

3. i+siz[x]是另一个子树的意思(i~i+siz[x]-1这段连续区间代表一颗子树内的所有点

技术图片
#include<bits/stdc++.h>
using namespace std;
#define eps 1e-6
#define ri register int
#define N 1005
double dp[N][N],val[N],b[N],c[N];
int dfn[N],head[N],to[N],nex[N],Ti=0,a[N],n,m,siz[N],d,tot=0,pos[N];
void add(int a,int b) { to[++tot]=b; nex[tot]=head[a]; head[a]=tot; }
void dfs(int u)
{
    dfn[u]=Ti; pos[Ti++]=u; siz[u]=1;//0!!! 一定要赋予0 dfs序!!!0是必选点 
    for(ri i=head[u];i;i=nex[i]){
        int v=to[i];
        dfs(v);
        siz[u]+=siz[v];
    }
}

bool check(double x)
{
    for(ri i=1;i<=n;++i) val[i]=b[i]-c[i]*x;
    memset(dp,0xc2,sizeof(dp));
    dp[n+1][0]=0;
    for(ri i=n;i>=0;--i){//模拟dfs回溯的倒序遍历 0是没有贡献的必选点 
        double tmp=-1e100;
        int x=pos[i];
        /*
        预处理j==m的情况,可以由子树选>=m-a[x]的物品转移过来,也可以由另一颗子树转移过来
        +val[x]强行选择了根 
        */
        for(ri j=max(0,m-a[x]);j<=m;++j) tmp=max(tmp,dp[i+1][j]);
        dp[i][m]=max(dp[i+siz[x]][m],tmp+val[x]);
        for(ri j=m-1;j>=0;--j){//记得从m-1开始!!! 
            if(j<a[x]) dp[i][j]=dp[i+siz[x]][j];
            else dp[i][j]=max(dp[i+1][j-a[x]]+val[x],dp[i+siz[x]][j]);
        }
    }
    return dp[0][m]>=eps;
}

int main()
{
    freopen("gift.in", "r", stdin);
    freopen("gift.out", "w", stdout);
    scanf("%d%d",&n,&m);
    for(ri i=1;i<=n;++i) scanf("%d%lf%lf%d",&a[i],&b[i],&c[i],&d),add(d,i);
    dfs(0);
    double l=0,r=4e4,ans=0;
    while(l+eps<r){
        double mid=(l+r)/2.0;
        if(check(mid)) l=mid+eps,ans=mid;
        else r=mid;
    }
    printf("%.3lf
",l);
}
View Code

 

以上是关于机房测试7:gift (树形背包)的主要内容,如果未能解决你的问题,请参考以下文章

机房测试9:gift(单调队列优化dp)

Henu ACM Round#17 CKitahara Haruki's Gift

[填坑][支线任务]树形DP 树形背包

NOIP2017模拟赛 senior 6.29 T3 Gift(gift)

hdu1561 树形dp+背包

HDU-1011 Starship Troopers (树形DP+分组背包)