「树上背包」可怜与超市

可怜与超市

发现商品间的关系显然是棵树,所以做树上背包。

发现商品的价格都很高不能放在状态里,而价值都是1,所以转换维度。

定义\(dp[u][j][0/1]\)为在\(u\)的子树中选出\(j\)个物品所需要的最小价格,其中0代表不用优惠券买u,1代表用优惠券买u。

转移:

\[\sum_{j = 0}^{siz[u] - siz[v]} \]

\[\sum_{k = 0}^{siz[v]} \]

\[dp[u][j + k][1] = min(dp[u][j + k][1], dp[u][j][1] + min(dp[v][k][1], dp[v][k][0])) \]

\[dp[u][j + k][0] = min(dp[u][j + k][0], dp[u][j][0] + dp[v][k][0]) \]

答案为 \(max(i)\) 满足 \(dp[1][i][1] <= b\)

#include <cstdio>
#include <algorithm>
#include <cstring>
#include <iostream>
using namespace std;
char buf[1 << 20], *p1 = buf, *p2 = buf;
char getc() {
    if (p1 == p2) {
	p1 = buf, p2 = buf + fread(buf, 1, 1 << 20, stdin);
	if (p1 == p2) return EOF;
    }
    return *p1++;
}
inline int read() {
    int s = 0, w = 1;
    char c = getc();
    while (c < '0' || c > '9') { if (c == '-') w = -1; c = getc(); }
    while (c >= '0' && c <= '9') s = s * 10 + c - '0', c = getc();
    return s * w;
}
const int maxn = 5e3 + 5;
int n, B;
struct edge {
    int nex, to;
}e[maxn * maxn];
int head[maxn], tot;
void Add(int u, int v) {
    e[++tot] = (edge) { head[u], v };
    head[u] = tot;
}
int dp[maxn][maxn][3];
int siz[maxn], c[maxn], d[maxn];
void Dfs(int u) {
    siz[u] = 1;
    for (int i = head[u]; i; i = e[i].nex) {
	int v = e[i].to;
	Dfs(v);
	siz[u] += siz[v];
    }
    return;
}
void DP(int u) {
    for (int i = head[u]; i; i = e[i].nex) {
	int v = e[i].to;
	DP(v);
	for (int j = siz[u] - siz[v]; j >= 0; j--) {
	    for (int k = siz[v]; k >= 0; k--) {
		dp[u][j + k][1] = min(dp[u][j + k][1], dp[u][j][1] + min(dp[v][k][1], dp[v][k][0]));
		dp[u][j + k][0] = min(dp[u][j + k][0], dp[u][j][0] + dp[v][k][0]);
	    }
	}
    }
}
int main() {
    n = read(), B = read();
    int fa;
    for (int i = 1; i <= n; i++) {
	if (i == 1) {
	    c[i] = read();
	    d[i] = read();
	}
	else {
	    c[i] = read();
	    d[i] = read();
	    fa = read();
	    Add(fa, i);
	}
    }
    Dfs(1);
    memset(dp, 0x3f, sizeof dp);
    for (int i = 1; i <= n; i++) {
	dp[i][1][1] = c[i] - d[i];
	dp[i][1][0] = c[i];
	dp[i][0][0] = 0;
    }
    DP(1);
    int ans;
    for (int i = 1; i <= n; i++) {
	if (dp[1][i][1] <= B)
	    ans = i;
    }
    printf("%d\n", ans);
    return 0;
}

另外,两个DFS可以合起来写,这样就相当于一次枚举一条边的两个端点,从各自的子树中去选择。

#include <cstdio>
#include <algorithm>
#include <cstring>
#include <iostream>
using namespace std;
char buf[1 << 20], *p1 = buf, *p2 = buf;
char getc() {
    if (p1 == p2) {
	p1 = buf, p2 = buf + fread(buf, 1, 1 << 20, stdin);
	if (p1 == p2) return EOF;
    }
    return *p1++;
}
inline int read() {
    int s = 0, w = 1;
    char c = getc();
    while (c < '0' || c > '9') { if (c == '-') w = -1; c = getc(); }
    while (c >= '0' && c <= '9') s = s * 10 + c - '0', c = getc();
    return s * w;
}
const int maxn = 5e3 + 5;
int n, B;
struct edge {
    int nex, to;
}e[maxn * maxn];
int head[maxn], tot;
void Add(int u, int v) {
    e[++tot] = (edge) { head[u], v };
    head[u] = tot;
}
int dp[maxn][maxn][3];
int siz[maxn], c[maxn], d[maxn];
void DP(int u) {
    siz[u] = 1;
    for (int i = head[u]; i; i = e[i].nex) {
	int v = e[i].to;
	DP(v);
	for (int j = siz[u]; j >= 0; j--) {
	    for (int k = siz[v]; k >= 0; k--) {
		dp[u][j + k][1] = min(dp[u][j + k][1], dp[u][j][1] + min(dp[v][k][1], dp[v][k][0]));
		dp[u][j + k][0] = min(dp[u][j + k][0], dp[u][j][0] + dp[v][k][0]);
	    }
	}
	siz[u] += siz[v];
    }
}
int main() {
    n = read(), B = read();
    int fa;
    for (int i = 1; i <= n; i++) {
	if (i == 1) {
	    c[i] = read();
	    d[i] = read();
	}
	else {
	    c[i] = read();
	    d[i] = read();
	    fa = read();
	    Add(fa, i);
	}
    }
    //Dfs(1);
    memset(dp, 0x3f, sizeof dp);
    for (int i = 1; i <= n; i++) {
	dp[i][1][1] = c[i] - d[i];
	dp[i][1][0] = c[i];
	dp[i][0][0] = 0;
    }
    DP(1);
    int ans;
    for (int i = 1; i <= n; i++) {
	if (dp[1][i][1] <= B)
	    ans = i;
    }
    printf("%d\n", ans);
    return 0;
}
posted @ 2020-09-24 20:06  zfio  阅读(151)  评论(0编辑  收藏  举报