题解 HDU6566 【The Hanged Man】

题意为给定 n 个点的树,每个节点为一个物品,有体积和价值,选物品必须满足不相邻,即选出一个独立集,求对于 i[1,m],容量为 i 时的背包最大价值的方案数。

1n50,1m5000

暴力就是直接树形背包,fi,j,0/1 为在 i 的子树内,容量为 j,是否选 i 的方案数,但复杂度为 O(nm2),无法接受。

考虑用 dfs 序转移来优化,但是发现从 dfs 序中 i 转移到 i+1 时,若 i+1 对应的节点在 i 对应的节点的上方时,就可能不知道 i+1 的父亲选择的情况:

因此还需知道像图中 i+1 的父亲那样的转折点的选择情况,直接状压一个点到根节点路径上的所有转折点不现实,会被链卡成状态数为 O(2n)

考虑优化状态数,先进行重链剖分,剖分重链时优先遍历轻儿子,因为最后才遍历重儿子,所以一个点到根节点的所有转折点都是一条重链的链顶的父亲,那么再进行状压,状态数就为 O(2logn)=O(n) 了。

fi,S,j 为考虑到 dfs 序中第 i 个点,到根的转折点的状态为 S,容量为 j 的方案数,转移是 O(1) 的,复杂度为 O(n2m)

另外一个做法是进行点分治,将每次的分治中心作为转折点来状压,这样状态数也是 O(n) 的。

#include<bits/stdc++.h> #define maxn 210 #define maxm 5010 using namespace std; typedef long long ll; template<typename T> inline void read(T &x) { x=0;char c=getchar();bool flag=false; while(!isdigit(c)){if(c=='-')flag=true;c=getchar();} while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=getchar();} if(flag)x=-x; } int T,n,m,cnt,now; int w[maxn],v[maxn],id[maxn]; int siz[maxn],fa[maxn],son[maxn],top[maxn],rev[maxn]; vector<int> t[maxn]; struct edge { int to,nxt; }e[maxn]; int head[maxn],edge_cnt; void add(int from,int to) { e[++edge_cnt]={to,head[from]},head[from]=edge_cnt; } struct node { int v; ll cnt; }f[2][maxn][maxm],ans[maxm]; node operator +(const node &x,const int &val) { return {x.v+val,x.cnt}; } void mx(node &x,node y) { if(x.v<y.v) x=y; else if(x.v==y.v) x.cnt+=y.cnt; } void dfs_son(int x,int fath) { fa[x]=fath,siz[x]=1; for(int i=head[x];i;i=e[i].nxt) { int y=e[i].to; if(y==fath) continue; dfs_son(y,x),siz[x]+=siz[y]; if(siz[y]>siz[son[x]]) son[x]=y; } } void dfs_chain(int x,int tp) { top[x]=tp,rev[++cnt]=x; for(int i=head[x];i;i=e[i].nxt) { int y=e[i].to; if(y==fa[x]||y==son[x]) continue; dfs_chain(y,y); } if(son[x]) dfs_chain(son[x],tp); } void clear() { edge_cnt=cnt=0,now=1; memset(f,0,sizeof(f)); memset(ans,0,sizeof(ans)); memset(son,0,sizeof(son)); memset(head,0,sizeof(head)); for(int i=1;i<=n;++i) t[i].clear(); } void solve(int num) { read(n),read(m),clear(); for(int i=1;i<=n;++i) read(w[i]),read(v[i]); for(int i=1;i<n;++i) { int x,y; read(x),read(y); add(x,y),add(y,x); } dfs_son(1,0),dfs_chain(1,1); for(int i=1;i<=n;++i) { int p=i; t[i].push_back(i); while(fa[top[p]]) p=fa[top[p]],t[i].push_back(p); } f[0][0][0]={0,1}; for(int i=1;i<=n;++i) { for(int s=0;s<(1<<t[rev[i]].size());++s) for(int j=0;j<=m;++j) f[now][s][j]={0,0}; int fath=20; for(int j=0;j<t[rev[i-1]].size();++j) { int p=t[rev[i-1]][j]; if(fa[rev[i]]==p) fath=j; id[j]=-1; for(int k=0;k<t[rev[i]].size();++k) if(p==t[rev[i]][k]) id[j]=k; } for(int s=0;s<(1<<t[rev[i-1]].size());++s) { int S=0; for(int j=0;j<t[rev[i-1]].size();++j) if((s&(1<<j))&&id[j]!=-1) S|=1<<id[j]; for(int j=0;j<=m;++j) { if(!f[now^1][s][j].cnt) continue; mx(f[now][S][j],f[now^1][s][j]); if((s&(1<<fath))||j+w[rev[i]]>m) continue; mx(f[now][S+1][j+w[rev[i]]],f[now^1][s][j]+v[rev[i]]); } } now^=1; } for(int s=0;s<(1<<t[rev[n]].size());++s) for(int i=1;i<=m;++i) mx(ans[i],f[now^1][s][i]); printf("Case %d:\n",num); for(int i=1;i<=m;++i) printf("%lld%c",ans[i].cnt," \n"[i==m]); } int main() { read(T); for(int i=1;i<=T;++i) solve(i); return 0; }

__EOF__

本文作者lhm_
本文链接https://www.cnblogs.com/lhm-/p/13697304.html
关于博主:sjzez 的一名 OI 学生
版权声明:转载标明出处
声援博主:希望得到宝贵的建议
posted @   lhm_liu  阅读(963)  评论(5编辑  收藏  举报
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示