方法挖掘——P1270 “访问”美术馆
题面
题目描述
经过数月的精心准备,Peer Brelstet,一个出了名的盗画者,准备开始他的下一个行动。艺术馆的结构,每条走廊要么分叉为两条走廊,要么通向一个展览室。Peer知道每个展室里藏画的数量,并且他精确测量了通过每条走廊的时间。由于经验老到,他拿下一幅画需要5秒的时间。你的任务是编一个程序,计算在警察赶来之前,他最多能偷到多少幅画。
输入输出格式
输入格式:
第1行是警察赶到的时间,以s为单位。第2行描述了艺术馆的结构,是一串非负整数,成对地出现:每一对的第一个数是走过一条走廊的时间,第2个数是它末端的藏画数量;如果第2个数是0,那么说明这条走廊分叉为两条另外的走廊。数据按照深度优先的次序给出,请看样例。
一个展室最多有20幅画。通过每个走廊的时间不超过20s。艺术馆最多有100个展室。警察赶到的时间在10min以内。
输出格式:
输出偷到的画的数量
输入输出样例
输入样例
60
7 0 8 0 3 1 14 2 10 0 12 4 6 2
输出样例
2
头脑风暴
纪念我为数不多的独立A掉的一道蓝题。
显然路径是二叉树一棵树,那么我就想到了树形dp。
不过这道题居然还考我语文。。。
正确解法
建树:这个边权和点权好像有点乱,于是我把走廊当成点来看,展览室里的画就属于通往该展览室的走廊的点权的一部分。这样再把根连一个点表示大门,就成了一棵根的度为1,其他结点度为2的树了。
方程:设
注意答案是dp[0][t-1],因为题目说的是“在警察来之前”,dp[0][t]警察已经来了,是“警察来之时”,不合题意(这个点肯定坑了好多人)。
因为循环层数好像有点高,还分析了一下复杂度。每个点遍历一次,每次都要访问t个时间,然后非叶结点要枚举时间,所以复杂度是
算法实现
#include <cstdio>
#define max(x, y) x > y ? x : y
const int maxn = 300;
const int maxt = 610;
int t;
int dp[maxn][maxt];
struct Tree {
int t, n, l, r;
} tree[maxn];
int m = 0;
int Build() {
int tt, n, id = ++m;
scanf("%d%d", &tt, &n);
tree[id].t = tt, tree[id].n = n;
if (!n) {
tree[id].l = Build();
tree[id].r = Build();
}
return id;
}
void dfs(int v) {
if (v == 0) {
dfs(1);
for (int j = t; j >= tree[1].t << 1; j--)
dp[v][j] = dp[1][j - (tree[1].t << 1)];
return;
}
if (tree[v].n) {
int n = tree[v].n;
for (int j = t; j >= 0; j--)
dp[v][j] = j/5 < n ? j/5 : n;
return;
}
int lc = tree[v].l, rc = tree[v].r;
int lt = tree[lc].t << 1, rt = tree[rc].t << 1;
dfs(lc), dfs(rc);
for (int j = t; j >= 0; j--) {
int ans = 0;
if (j >= lt)
ans = max(ans, dp[lc][j-lt]);
if (j >= rt)
ans = max(ans, dp[rc][j-rt]);
if (j >= lt + rt) {
for (int k = 0; k <= j-lt-rt; k++)
ans = max(ans, dp[lc][k] + dp[rc][j-lt-rt-k]);
}
dp[v][j] = ans;
}
}
int main() {
scanf("%d", &t);
Build();
dfs(0);
printf("%d", dp[0][t-1]);
return 0;
}