题意为给定 n 个点的树,每个节点为一个物品,有体积和价值,选物品必须满足不相邻,即选出一个独立集,求对于 ∀i∈[1,m],容量为 i 时的背包最大价值的方案数。
1⩽n⩽50,1⩽m⩽5000
暴力就是直接树形背包,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__
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现