【洛谷1273】有线电视网(树上背包)
大致题意: 给你一棵带权树,已知每连接一条边需要一定花费,如果某个叶节点能到达根,可以获得一定收益。问在不亏本的情况下,最多能使多少个叶节点能到达根。
树上背包
这是一道比较经典的树上背包题。
如何记录状态
我们可以用\(f_{i,j}\)表示在以\(i\)为根的子树内选择\(j\)个叶节点能得到的最大收益,并用\(Size_i\)表示以\(i\)为根的子树内的叶节点数目。
那么答案就是找到一个最大的\(ans\)使得\(f_{1,ans}≥0\)。
如何转移
树上背包的转移与普通背包是十分相似的:
\[f_{x,j}=max(f_{x,j},f_{x,j-k}+f_{son,k}-val)
\]
其中\(val\)表示这条边的边权。
注意\(j\)和\(k\)的枚举范围,其中\(1≤j≤Size_x+Size_{son},1≤k≤Size_{son}\)。
虽然复杂度看似\(O(n^3)\)的,但实际上是达不到上界的,于是虽然\(1≤n≤3000\),依然能轻松跑过。
代码
#include<bits/stdc++.h>
#define max(x,y) ((x)>(y)?(x):(y))
#define min(x,y) ((x)<(y)?(x):(y))
#define uint unsigned int
#define LL long long
#define ull unsigned long long
#define swap(x,y) (x^=y,y^=x,x^=y)
#define abs(x) ((x)<0?-(x):(x))
#define INF 1e9
#define Inc(x,y) ((x+=(y))>=MOD&&(x-=MOD))
#define ten(x) (((x)<<3)+((x)<<1))
#define N 3000
#define add(x,y,z) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y,e[ee].val=z)
using namespace std;
int n,m,ee=0,lnk[N+5],val[N+5];
struct edge
{
int to,nxt,val;
}e[N+5];
class FIO
{
private:
#define Fsize 100000
#define tc() (FinNow==FinEnd&&(FinEnd=(FinNow=Fin)+fread(Fin,1,Fsize,stdin),FinNow==FinEnd)?EOF:*FinNow++)
#define pc(ch) (FoutSize<Fsize?Fout[FoutSize++]=ch:(fwrite(Fout,1,FoutSize,stdout),Fout[(FoutSize=0)++]=ch))
int f,FoutSize,OutputTop;char ch,Fin[Fsize],*FinNow,*FinEnd,Fout[Fsize],OutputStack[Fsize];
public:
FIO() {FinNow=FinEnd=Fin;}
inline void read(int &x) {x=0,f=1;while(!isdigit(ch=tc())) f=ch^'-'?1:-1;while(x=ten(x)+(ch&15),isdigit(ch=tc()));x*=f;}
inline void read_char(char &x) {while(isspace(x=tc()));}
inline void read_string(string &x) {x="";while(isspace(ch=tc()));while(x+=ch,!isspace(ch=tc())) if(!~ch) return;}
inline void write(int x) {if(!x) return (void)pc('0');if(x<0) pc('-'),x=-x;while(x) OutputStack[++OutputTop]=x%10+48,x/=10;while(OutputTop) pc(OutputStack[OutputTop]),--OutputTop;}
inline void write_char(char x) {pc(x);}
inline void write_string(string x) {register int i,len=x.length();for(i=0;i<len;++i) pc(x[i]);}
inline void end() {fwrite(Fout,1,FoutSize,stdout);}
}F;
class Class_TreeDP//树形DP求解树上背包
{
private:
int f[N+5][N+5],Size[N+5];//f[i][j]表示在以i为根的子树内选择j个叶节点能得到的最大收益,Size[i]表示以i为根的子树内的叶节点数目
inline void DP(int x)//DP的核心过程
{
register int i,j,k;
for(i=1;i<=m;++i) f[x][i]=-INF;//初始化
if(x>n-m) return (void)(f[x][1]=val[x],Size[x]=1);//如果当前节点是叶节点,修改f[x][1]为val[x],Size[x]为1,然后return
for(i=lnk[x];i;i=e[i].nxt)//枚举子节点
{
for(DP(e[i].to),j=Size[x]+Size[e[i].to];j;--j)//背包转移
for(k=1;k<=Size[e[i].to];++k) f[x][j]=max(f[x][j],f[x][j-k]+f[e[i].to][k]-e[i].val);
Size[x]+=Size[e[i].to];//将Size[x]加上Size[e[i].to]
}
}
public:
inline int GetAns()
{
register int i;
for(DP(1),i=m;i;--i) if(f[1][i]>=0) return i;//找到最大的i使f[1][i]>=0
return 0;
}
}TreeDP;
int main()
{
register int i,t,x,y;
for(F.read(n),F.read(m),i=1;i<=n-m;++i) for(F.read(t);t;--t) F.read(x),F.read(y),add(i,x,y);
for(;i<=n;++i) F.read(val[i]);
return F.write(TreeDP.GetAns()),F.end(),0;
}
待到再迷茫时回头望,所有脚印会发出光芒