把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【洛谷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;
}
posted @ 2018-10-28 16:06  TheLostWeak  阅读(220)  评论(0编辑  收藏  举报