BZOJ 4182 Shopping (点分治+树上多重背包)

题目大意:给你一颗树,你有$m$元钱,每个节点都有一种物品,价值为$w$,代价为$c$,有$d$个,如果在$u$和$v$两个城市都购买了至少一个物品,那么$u,v$路径上每个节点也都必须买至少一个物品

单调队列数组开小了调了2h

通过这道题,本蒟蒻终于$get$到了树上带权背包的正确姿势

合并背包的代价是$O(m^{2})$的,非常不友好,而在序列上处理背包时,是不需要合并背包的,所以我们把树拍成$dfs$序

显然,树上背包需要用子节点更新父节点的信息,所以倒序枚举时间戳$i$

设$f[i][j]$表示时间戳为$i$,总代价为$j$时,所有时间戳$>=i$的节点,树上背包能得到的最大价值,令$x$表示时间戳为$i$的节点编号

如果不选节点$x$,那么它子树内的节点都不能选,为了保证$f[i]$是$i$后面所有节点构成的最优解,所以跳过$x$子树的状态,用$f[ed_{x}+1]$更新$f[i]$,$ed_{x}$表示节点$x$的出栈时间

如果选节点$x$,那么可以选$x$的子树内的节点,用$f[i+1]$更新$f[i]$

两者取最优解即可

树上带权$01$背包的时间被我们优化成了$O(nm)$

多重背包可以用单调队列优化,时间一样是$O(nm)$

而上面的$dp$方程仅适用于必须链并经过根节点的情况

所以使用点分治每次选择一个重心作为根跑$DP$

总时间$O(Tnmlogn)$

代码巨丑

  1 #include <cmath>
  2 #include <vector>
  3 #include <cstdio>
  4 #include <cstring>
  5 #include <algorithm>
  6 #define N1 510
  7 #define M1 4010
  8 #define ll long long
  9 #define inf 233333333
 10 using namespace std;
 11 
 12 int gint()
 13 {
 14     int ret=0,fh=1;char c=getchar();
 15     while(c<'0'||c>'9'){if(c=='-')fh=-1;c=getchar();}
 16     while(c>='0'&&c<='9'){ret=ret*10+c-'0';c=getchar();}
 17     return ret*fh;
 18 }
 19 int n,K,T;
 20 struct Edge{
 21 int to[M1],nxt[M1],head[N1],cte;
 22 void ae(int u,int v)
 23 {cte++;to[cte]=v,nxt[cte]=head[u],head[u]=cte;}
 24 }E;
 25 
 26 int W[N1],C[N1],D[N1];
 27 int st[N1],ed[N1],id[N1],tot;
 28 int sz[N1],lim[N1];
 29 int use[N1],mi,G;
 30 void gra(int u,int dad,int szfa)
 31 {
 32     int j,v,ma=szfa;
 33     if(szfa>mi) return;
 34     for(j=E.head[u];j;j=E.nxt[j])
 35     {
 36         v=E.to[j]; if(use[v]||v==dad) continue;
 37         ma=max(ma,sz[v]);
 38         gra(v,u,szfa+sz[u]-sz[v]);
 39     }
 40     if(ma<mi) mi=ma,G=u;
 41 }
 42 void dfs_pre(int u,int dad)
 43 {
 44     int j,v; sz[u]=0;
 45     st[u]=++tot; id[tot]=u;
 46     for(j=E.head[u];j;j=E.nxt[j])
 47     {
 48         v=E.to[j]; if(use[v]||v==dad) continue;
 49         lim[v]=lim[u]-C[u]; 
 50         dfs_pre(v,u); sz[u]+=sz[v];
 51     }
 52     sz[u]++; ed[u]=tot;
 53 }
 54 int que[M1],hd,tl;
 55 int f[N1][M1],ans,de;
 56 void calc(int u)
 57 {
 58     int i,j,k,p,x,w,d,c;
 59     memset(f[tot+1],0,sizeof(f[tot+1]));
 60     for(i=tot;i;i--)
 61     {
 62         x=id[i]; c=C[x]; d=D[x]; w=W[x]; 
 63         memcpy(f[i],f[ed[x]+1],sizeof(f[i]));
 64         for(j=0;j<c;j++)
 65         {
 66             hd=1,tl=0,que[++tl]=0;
 67             for(k=1;k*c+j<=lim[x];k++)
 68             {
 69                 while(hd<=tl&&k-que[hd]>d)
 70                     hd++;
 71                 f[i][k*c+j]=max(f[i][k*c+j],f[i+1][que[hd]*c+j]+(k-que[hd])*w);
 72                 while(hd<=tl&&f[i+1][k*c+j]-k*w>=f[i+1][que[tl]*c+j]-que[tl]*w)
 73                     tl--;
 74                 que[++tl]=k;
 75             }
 76         }
 77     }
 78     for(j=0;j<=K;j++) ans=max(ans,f[1][j]);
 79 }
 80 void main_dfs(int u)
 81 {
 82     int j,v;
 83     use[u]=1; tot=0,lim[u]=K;
 84     dfs_pre(u,-1);
 85     calc(u);
 86     for(j=E.head[u];j;j=E.nxt[j])
 87     {
 88         v=E.to[j]; if(use[v]) continue;
 89         mi=inf,G=0,gra(v,u,0);
 90         main_dfs(G);
 91     }
 92 }
 93 void MAIN()
 94 {
 95     dfs_pre(1,-1); 
 96     mi=inf,G=0,gra(1,-1,0);
 97     main_dfs(G);
 98 }
 99 void init()
100 {
101     tot=0,E.cte=0,ans=0;
102     memset(use,0,sizeof(use));
103     memset(E.head,0,sizeof(E.head));
104 }
105 
106 int main()
107 {
108     freopen("t2.in","r",stdin);
109     scanf("%d",&T);
110     while(T--)
111     {
112         scanf("%d%d",&n,&K);
113         int i,x,y,z; init();
114         for(i=1;i<=n;i++) W[i]=gint();
115         for(i=1;i<=n;i++) C[i]=gint();
116         for(i=1;i<=n;i++) D[i]=gint();
117         for(i=1;i<n;i++)
118         {
119             x=gint(), y=gint();
120             E.ae(x,y),E.ae(y,x);
121         }
122         MAIN();
123         printf("%d\n",ans);
124     }
125     return 0;
126 }

 

posted @ 2018-12-26 18:38  guapisolo  阅读(150)  评论(0编辑  收藏