Breakdown(分解)
时间限制:2s 内存限制:1024MB
【原题地址】
所有图片源自Atcoder,题目译文源自脚本Atcoder Better!
【问题描述】
【输入格式】
【输出格式】
【样例1】
【样例输入1】
6 6
1 2
2 3
3 1
3 4
1 5
5 6
9 2 3 1 4 4
1 0 0 0 0 1
【样例输出1】
5
【样例说明1】
【样例2】
【样例输入2】
2 1
1 2
1 2
0 0
【样例输出2】
0
【样例说明2】
【样例3】
【样例输入3】
10 20
4 8
1 10
1 7
5 9
9 10
8 10
7 5
1 4
7 3
8 7
2 8
5 8
4 2
5 1
7 2
8 3
3 4
8 9
7 10
2 3
25 5 1 1 16 5 98 3 21 1
35 39 32 11 35 37 14 29 36 1
【样例输出3】
1380
【解题思路】
老汉使用到的是dp的解题方式
本题是求按照题目操作消除所有棋子时,最大的操作数。
本题消除该点的棋子时,会为下一关系点(通过边连接的点),放置一个棋子,我们只需求出在该点放置一个点时,完全消除它需要的最大操作数,最后再乘上该点拥有的棋子数就是最终答案。由于题目限制,只有当关系点的w总值小于该点的w值时,才可以进行放置棋子操作,这就有点类似背包求最大个数的题型,再加上w值大的点与w值小的点有着上下级关系,就有点树形的感觉,所以我们可以把该题看成一个树形背包问题,通过dfs遍历树,进行更新对应点的最大值。
代码注释有详细过程
【代码】
package ABC341_F_Breakdown;
import java.io.*;
import java.util.*;
public class Main {
// 存储相互连接的两个点
static Vector<Integer>[] g;
// 存储对应点的w值
static int[] w;
// 存储在该点有一个棋子时,操作的最大次数
static int[] dp;
static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
static PrintWriter pw = new PrintWriter(new OutputStreamWriter(System.out));
static StreamTokenizer st = new StreamTokenizer(br);
public static int readInt() throws IOException {
st.nextToken();
return (int) st.nval;
}
public static void main(String[] args) throws IOException {
int n = readInt();
int m = readInt();
g = new Vector[n + 1];
for (int i = 0; i <= n; i++) {
g[i] = new Vector<Integer>();
}
// 存储相互连接的点
for (int i = 1; i <= m; i++) {
int u = readInt();
int v = readInt();
g[u].add(v);
g[v].add(u);
}
w = new int[n + 1];
dp = new int[n + 1];
for (int i = 1; i <= n; i++) {
w[i] = readInt();
}
// 当该点的值没有更新时,dfs更新一系列相关点的dp值
for (int i = 1; i <= n; i++) {
if (dp[i] == 0) {
dfs(i, 0);
}
}
// 存放总操作数
long ans = 0;
int[] a = new int[n + 1];
for (int i = 1; i <= n; i++) {
a[i] = readInt();
// 求总操作数
ans += (long) a[i] * dp[i];
}
pw.println(ans);
pw.flush();
pw.close();
br.close();
}
/**
* 更新从该点开始,一系列相关点的dp值 注释:从头遍历到尾,再从尾部开始逐步向上跟新dp值
*
* @param x 当前更新点
* @param fa 上一点
*/
public static void dfs(int x, int fa) {
// dp值至少为1
dp[x] = 1;
// 存放消除该点棋子,分配给关系点后的最大操作次数
int max = 0;
// 存放对应背包容量时,最大的操作次数(背包容量为w[x]-1)
int[] dp2 = new int[w[x]];
// 遍历该点对应的关系点
for (int i = 0; i < g[x].size(); i++) {
int y = g[x].get(i);
// 当遍历点为上一点时,跳过本次遍历
if (y == fa)
continue;
// 当该点下一级关系点的dp未更新时,进行遍历更新
if (w[x] > w[y] && dp[y] == 0)
dfs(y, x);
// 滚动数组,求背包最大值
for (int j = w[x] - w[y] - 1; j >= 0; j--) {
if (dp2[j] != 0 || j == 0) {
dp2[j + w[y]] = Math.max(dp2[j + w[y]], dp2[j] + dp[y]);
max = Math.max(max, dp2[j + w[y]]);
}
}
}
// 加上关系点的最大操作数即为该点最大操作数
dp[x] += max;
}
}