zoukankan      html  css  js  c++  java
  • luogu2014 选课[树形背包][优化成$O(n^2)$的方法]

    https://www.luogu.org/problemnew/show/P2014


    树形背包的裸题。。当版子好了。

    $f[i][j][k]$表示子树$i$选前$j$个孩子,共$k$个后代节点时的最大价值。然后$j$那一维是可以滚动的(但同时也要注意枚举变成了倒序),所以可以去掉。

    $f[i][j]$表示子树$i$共选$k$个后代节点时的最大价值。

    然后每个点可以抽象为一个背包,他的每个孩子包含一物品,一组物品中包括以孩子为子树,选v个其后代节点形成的最大价的共v+1个物品(1指的是只有孩子自己)。对于每个孩子,只能选他的一种状态情形,或者不选。所以就是一个分组背包啦。

    但是注意,子树的根必须强制选上。所以可以以他为初态,也就是后代节点=0的状态。写一下伪代码。

    $dp$  $i$

    $f_{i,0}=w_i$初态

    $for$  $j=1$ $sim$ $son_i$

      $for$  $k=$(倒序)$sum_i -1$ $sim$ $1$

        $for$  $v=0$ $sim$ $sum_j -1$

          $if$  $v+1leqslant k$

            $f_{i,k}=max{f_{i,k-v-1}+f_{j,v}}$

    然后复杂度由于是每个点都被考虑一次的,最坏是$O(N^3)$。(看做$N,M$同阶)

     1 #include<iostream>
     2 #include<cstdio>
     3 #include<cstring>
     4 #include<cmath>
     5 #include<algorithm>
     6 #include<queue>
     7 #define dbg(x) cerr<<#x<<" = "<<x<<endl
     8 #define ddbg(x,y) cerr<<#x<<" = "<<x<<"   "<<#y<<" = "<<y<<endl
     9 using namespace std;
    10 typedef long long ll;
    11 template<typename T>inline char MIN(T&A,T B){return A>B?A=B,1:0;}
    12 template<typename T>inline char MAX(T&A,T B){return A<B?A=B,1:0;}
    13 template<typename T>inline T _min(T A,T B){return A<B?A:B;}
    14 template<typename T>inline T _max(T A,T B){return A>B?A:B;}
    15 template<typename T>inline T read(T&x){
    16     x=0;int f=0;char c;while(!isdigit(c=getchar()))if(c=='-')f=1;
    17     while(isdigit(c))x=x*10+(c&15),c=getchar();return f?x=-x:x;
    18 }
    19 const int N=300+7;
    20 int f[N][N],Head[N],Next[N<<1],to[N<<1],w[N],sum[N],tot;
    21 int n,m;
    22 inline void Addedge(int x,int y){
    23     to[++tot]=y,Next[tot]=Head[x],Head[x]=tot;
    24     to[++tot]=x,Next[tot]=Head[y],Head[y]=tot;
    25 }
    26 #define j to[tmp]
    27 void dp(int i,int fa){
    28     sum[i]=1;
    29     for(register int tmp=Head[i];tmp;tmp=Next[tmp])if(j!=fa)dp(j,i),sum[i]+=sum[j];
    30     f[i][0]=w[i];
    31     for(register int tmp=Head[i];tmp;tmp=Next[tmp])if(j!=fa){
    32         for(register int k=sum[i]-1;k;--k){
    33             for(register int v=0;v<=sum[j]-1;++v)
    34                 if(v+1<=k)MAX(f[i][k],f[i][k-v-1]+f[j][v]);
    35         }
    36     }
    37 }
    38 #undef j
    39 int main(){//freopen("test.in","r",stdin);//freopen("test.out","w",stdout);
    40     read(n),read(m);int x;
    41     for(register int i=1;i<=n;++i)read(x),read(w[i]),Addedge(x,i);
    42     dp(0,0);printf("%d
    ",f[0][m]);
    43     return 0;
    44 }
    View Code

    然而这题有更优秀的(优化)做法。复杂度(看做$N,M$同阶)都是$O(N^2)$.

    1.根据上面的一种针对性优化。

    由于上面每次合并$i$的答案可以视作是子树$j$和$i$的已合并部分做一个$f[i,k+v]<--f[i,k]+f[j,v]$。同时又有“每个点费用都是$1$”这样一个隐含的特殊条件,

    所以如果每次合并都枚举整棵树大小的费用未免浪费。把枚举改成:枚举$i$已合并部分点的个数$k$和价值$f[i,k]$,再枚举子树$j$的点个数$v$和价值$f[j,v]$,更新之。

    就是这样。比较一下和上面的这个的区别。

     1 #include<iostream>
     2 #include<cstdio>
     3 #include<cstring>
     4 #include<cmath>
     5 #include<algorithm>
     6 #include<queue>
     7 #define dbg(x) cerr<<#x<<" = "<<x<<endl
     8 #define ddbg(x,y) cerr<<#x<<" = "<<x<<"   "<<#y<<" = "<<y<<endl
     9 using namespace std;
    10 typedef long long ll;
    11 template<typename T>inline char MIN(T&A,T B){return A>B?A=B,1:0;}
    12 template<typename T>inline char MAX(T&A,T B){return A<B?A=B,1:0;}
    13 template<typename T>inline T _min(T A,T B){return A<B?A:B;}
    14 template<typename T>inline T _max(T A,T B){return A>B?A:B;}
    15 template<typename T>inline T read(T&x){
    16     x=0;int f=0;char c;while(!isdigit(c=getchar()))if(c=='-')f=1;
    17     while(isdigit(c))x=x*10+(c&15),c=getchar();return f?x=-x:x;
    18 }
    19 const int N=10000+7;
    20 int f[N][N],Head[N],Next[N<<1],to[N<<1],w[N],sum[N],tot;
    21 int n,m;
    22 inline void Addedge(int x,int y){
    23     to[++tot]=y,Next[tot]=Head[x],Head[x]=tot;
    24     to[++tot]=x,Next[tot]=Head[y],Head[y]=tot;
    25 }
    26 #define j to[tmp]
    27 void dp(int i,int fa){
    28     sum[i]=1;f[i][0]=w[i];
    29     for(register int tmp=Head[i];tmp;tmp=Next[tmp])if(j!=fa){
    30         dp(j,i);
    31         for(register int k=sum[i]-1;~k;--k)
    32             for(register int v=0;v<=sum[j]-1;++v)
    33                 MAX(f[i][k+v+1],f[i][k]+f[j][v]);
    34         sum[i]+=sum[j];
    35     }
    36 }
    37 #undef j
    38 int main(){//freopen("test.in","r",stdin);freopen("test.out","w",stdout);
    39     read(n),read(m);int x;
    40     for(register int i=1;i<=n;++i)read(x),read(w[i]),Addedge(x,i);
    41     dp(0,0);printf("%d
    ",f[0][m]);
    42     return 0;
    43 }
    View Code

    树中每个点对相当于只会被在LCA处合并$f[lca,k+v]$枚举一次(可以把枚举的个数$k,v$看做子树中的编号)。于是枚举了$O(n^2)$个点对。所以是平方复杂度。

    但是,这只是针对性的优化,也就是说,如果改成每个点都有一个费用且不一定为1,这样每个点做一次分组背包时就要完全枚举费用这一维了,没有办法用点对优化。

    2.更高效的dfs序优化。

    给定一棵 $n $个节点的树,$1$ 号节点是根节点。每个点有一个物品,价格为 $c_i$ ,价值为 $v_i$ 。
    要买一个点上的物品,必须先把它父节点的物品给买了。求 $m$ 元钱能买到的最大价值。$n,m ≤ 2000$。

    这时无法用点对优化。因为树上dp是按照一定顺序(dfs序)进行的,所以考虑转化到dfs序列上处理。设得到的dfs序中,$i$对应原序列点编号$p_i$,这个$p_i$子树dfs序上右端点设为$r_i$。

    设$f_{i,j}$为dfs序上选择$isim n$中的点且满足树形要求的、费用为$j$的最大价值。

    则$f_{i,j}=max(f_{i+1,j-cost_{p_i}}+value_{p_i},f_{r_i+1,j})$。

    注意是倒序以使得先处理所有子代再处理子树根的。相当于决定当前子树的根如果选,那么他的子树内部和后面的dfs序都可以随便选。如果不选,那子树这一段的dfs序都不可选,直接从另一颗子树中继承过来。

    可知若后面的点$i+1$若满足树形依赖的要求,则dp了$i$之后$i$只可能包含这个子树没选和$i$和$i+1$都选了($i+1 sim r_i$满足树形依赖,则选$i$后也应当满足依赖关系)的情况。

     1 #include<iostream>
     2 #include<cstdio>
     3 #include<cstring>
     4 #include<algorithm>
     5 #include<cmath>
     6 #define dbg(x) cerr << #x << " = " << x <<endl
     7 using namespace std;
     8 typedef long long ll;
     9 typedef double db;
    10 typedef pair<int,int> pii;
    11 template<typename T>inline T _min(T A,T B){return A<B?A:B;}
    12 template<typename T>inline T _max(T A,T B){return A>B?A:B;}
    13 template<typename T>inline char MIN(T&A,T B){return A>B?(A=B,1):0;}
    14 template<typename T>inline char MAX(T&A,T B){return A<B?(A=B,1):0;}
    15 template<typename T>inline void _swap(T&A,T&B){A^=B^=A^=B;}
    16 template<typename T>inline T read(T&x){
    17     x=0;int f=0;char c;while(!isdigit(c=getchar()))if(c=='-')f=1;
    18     while(isdigit(c))x=x*10+(c&15),c=getchar();return f?x=-x:x;
    19 }
    20 const int N=2000+7;
    21 int f[N][N],c[N],val[N];
    22 int n,m,cnt;
    23 struct thxorz{int to,nxt;}G[N<<1];
    24 int Head[N],id[N],ed[N],tot;
    25 inline void Addedge(int x,int y){
    26     G[++tot].to=y,G[tot].nxt=Head[x],Head[x]=tot;
    27     G[++tot].to=x,G[tot].nxt=Head[y],Head[y]=tot;
    28 }
    29 #define y G[j].to
    30 inline void dfs(int x,int fa){
    31     id[++cnt]=x;
    32     for(register int j=Head[x];j;j=G[j].nxt)if(y^fa)dfs(y,x);
    33     ed[x]=cnt;
    34 }
    35 #undef y
    36 int main(){//freopen("test.in","r",stdin);freopen("test.ans","w",stdout);
    37     read(n);read(m);
    38     for(register int i=1,x;i<=n;++i)read(x),read(val[i]),Addedge(x,i),c[i]=1;//read(c[i]);
    39     dfs(0,0);
    40     for(register int i=cnt;i;--i)
    41         for(register int j=c[id[i]];j<=m;++j)
    42             f[i][j]=_max(f[i+1][j-c[id[i]]]+val[id[i]],f[ed[id[i]]+1][j]);//dbg(i),dbg(j),dbg(f[i][j]);
    43     printf("%d
    ",f[1][m]);
    44     return 0;
    45 }
    View Code

    还有一种转二叉树的做法,不想了解。

    这篇题解赶完了。

  • 相关阅读:
    生产者消费者问题 一个生产者 两个消费者 4个缓冲区 生产10个产品
    三个线程交替数数 数到100
    c++ 字符串去重
    Java中一个方法只被一个线程调用一次
    GEF开发eclipse插件,多页编辑器实现delete功能
    python-arp 被动信息收集
    ssrf
    TCP
    xxe
    越权
  • 原文地址:https://www.cnblogs.com/saigyouji-yuyuko/p/10700556.html
Copyright © 2011-2022 走看看