机房测试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 */
那道题中,定义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); }
以上是关于机房测试7:gift (树形背包)的主要内容,如果未能解决你的问题,请参考以下文章
Henu ACM Round#17 CKitahara Haruki's Gift