bzoj4182: Shopping 树上多重背包+点分治

bzoj4182: Shopping

Description

马上就是小苗的生日了,为了给小苗准备礼物,小葱兴冲冲地来到了商店街。商店街有n个商店,并且它们之间的道路构成了一颗树的形状。
第i个商店只卖第i种物品,小苗对于这种物品的喜爱度是wi,物品的价格为ci,物品的库存是di。但是商店街有一项奇怪的规定:如果在商店u,v买了东西,并且有一个商店w在u到v的路径上,那么必须要在商店w买东西。小葱身上有m元钱,他想要尽量让小苗开心,所以他希望最大化小苗对买
到物品的喜爱度之和。这种小问题对于小葱来说当然不在话下,但是他的身边没有电脑,于是他打电话给同为OI选手的你,你能帮帮他吗?

Input

输入第一行一个正整数T,表示测试数据组数。
对于每组数据,
第一行两个正整数n;m;
第二行n个非负整数w1,w2…wn;
第三行n个正整数c1,c2…cn;
第四行n个正整数d1,d2…dn;
接下来n-1行每行两个正整数u;v表示u和v之间有一条道路

Output

输出共T 行,每行一个整数,表示最大的喜爱度之和。

Sample Input

1
3 2
1 2 3
1 1 1
1 2 1
1 2
1 3

Sample Output

4

HINT

N<=500,M<=4000,T<=5,Wi<=4000,Di<=100

知识点:单调队列优化多重背包

首先写出多重背包的式子
F [ i ] [ j ] = max k l [ i ] { F [ i 1 ] [ j k v [ i ] ] + k w [ i ] }
然后关键的一步就是考虑使用在 mod v [ i ] 意义下的转移方程
于是有令 j = a v [ i ] + b 方程改为
F [ i ] [ j ] = max k l [ i ] { F [ i 1 ] [ ( a k ) v [ i ] + b ] + k w [ i ] }
t = a k k = a t
F [ i ] [ j ] = max { F [ i 1 ] [ t v [ i ] + b ] t w [ i ] } + a w [ i ] ( a l [ i ] t a )
我们先枚举 b ,再枚举 a ,再枚举 t
枚举 t 的过程可以用单调队列优化。
于是复杂度就可以降到 O ( n m )

多重背包优化代码

void Pack(int cur, int c, int v, int l) {
    for(int j = 0, La, Ra, Lb, Rb; j < c; ++j) {
        La = Ra = Lb = Rb = 0;
        for(int k = j, i = 0; k <= m; k += c, ++i) {
            if(Rb == Lb + l + 1)
                if(qa[La + 1] == qb[++Lb]) ++La; 
            int t = f[cur + 1][k] - i * v;
            while(La < Ra && qa[Ra] <= t) --Ra;
            qa[++Ra] = qb[++Rb] = t;
            f[cur][k] = qa[La + 1] + i * v;
        }
    }
}

分析

如果不考虑树,就是一道多重背包。
考虑树,可以枚举根,对于树块上的01背包,我们有一个经典的方程
f [ i ] [ j ] = max { f [ n x [ i ] ] [ j ] , f [ i + 1 ] [ j c [ i ] ] + w [ i ] }
也就是分成选或不选两个部分
对于多重背包,我们只需要特殊考虑选的过程。
转移基本相同,就是多了一个必须选的条件。
考虑多重背包里不选的情况。也就是 a = l [ i ] 的情况。
跳过这步转移即可。
然而这样的复杂度仍然是 O ( n 2 m )
上点分治即可。
总复杂度 O ( n m l o g n )

代码

/**************************************************************
    Problem: 4182
    User: 2014lvzelong
    Language: C++
    Result: Accepted
    Time:10428 ms
    Memory:9244 kb
****************************************************************/

#include<iostream>
#include<cstdlib>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
const int M = 4005, N = 505, inf = 0x3f3f3f3f;
int read() {
    char ch = getchar(); int x = 0, f = 1;
    for(;ch < '0' || ch > '9'; ch = getchar()) if(ch == '-') f = -1;
    for(;ch >= '0' && ch <= '9'; ch = getchar()) x = (x << 1) + (x << 3) + ch - '0';
    return x * f;
}
int siz[N], dfn[N], pre[N], c[N], l[N], v[N], qa[M], qb[M];
int nxt[N << 1], to[N << 1], f[N][M], ans, top, cnt, mx, G, m, n;
bool vis[N];
void add(int u, int v) {to[++top] = v; nxt[top] = pre[u]; pre[u] = top;}
void adds(int u, int v) {add(u, v); add(v, u);}
void Root(int u, int fa) {
    siz[u] = 1; int tp = 0;
    for(int i = pre[u]; i; i = nxt[i]) 
    if(to[i] != fa && !vis[to[i]]) {
        Root(to[i], u); siz[u] += siz[to[i]];
        tp = max(tp, siz[to[i]]);
    }
    tp = max(tp, cnt - siz[u]);
    if(tp < mx) mx = tp, G = u;
}
void Pack(int cur, int c, int v, int l) {
    for(int j = 0, La, Ra, Lb, Rb; j < c; ++j) {
        La = Ra = Lb = Rb = 0;
        for(int k = j, i = 0; k <= m; k += c, ++i) {
            if(Rb == Lb + l + 1)
                if(qa[La + 1] == qb[++Lb]) ++La; 
            if(La < Ra) f[cur][k] = qa[La + 1] + i * v;
            int t = f[cur + 1][k] - i * v;
            while(La < Ra && qa[Ra] <= t) --Ra;
            qa[++Ra] = qb[++Rb] = t;
        }
    }
}
void Dp() {
    for(int i = top, k; i; --i) {
        k = dfn[i]; Pack(i, c[k], v[k], l[k]);
        for(int j = 0;j <= m; ++j) f[i][j] = max(f[i][j], f[i + siz[k]][j]);
    }
    for(int i = 0;i <= m; ++i) ans = max(ans, f[1][i]);
    for(int i = 1;i <= top; ++i) for(int j = 0;j <= m; ++j) f[i][j] = 0;
}
void Dfs(int u, int fa) {
    siz[u] = 1; dfn[++top] = u;
    for(int i = pre[u]; i; i = nxt[i]) 
    if(to[i] != fa && !vis[to[i]]) Dfs(to[i], u), siz[u] += siz[to[i]];
}
void Div(int u) {
    top = 0; Dfs(u, 0); Dp(); vis[u] = true;
    for(int i = pre[u]; i; i = nxt[i])
        if(!vis[to[i]]) {
            cnt = siz[to[i]]; mx = inf; G = 0; 
            Root(to[i], 0); Div(G);
        }
}
int main() {
    for(int T = read(); T--;) {
        n = read(); m = read(); top = 0; ans = 0;
        for(int i = 1;i <= n; ++i) vis[i] = pre[i] = 0;
        for(int i = 1;i <= n; ++i) v[i] = read();
        for(int i = 1;i <= n; ++i) c[i] = read();
        for(int i = 1;i <= n; ++i) l[i] = read();
        for(int i = 1;i < n; ++i) adds(read(), read());
        G = 0; cnt = n; mx = inf; Root(1, 0);
        Div(G); printf("%d\n", ans);
    }
    return 0;
}


猜你喜欢

转载自blog.csdn.net/lvzelong2014/article/details/79594011