【发布时间】:2014-01-21 21:09:59
【问题描述】:
我一直在看一些 USACO 金牌级别的算法问题,我需要有人帮助向我解释这个问题的解决方案。 Here是问题所在,解决方法如下:
通常解决增量更新问题的第一步是在没有增量更新的情况下解决问题,但在这种情况下,它可能会导致一个死胡同。对于这个问题的非增量版本,有一个非常简单的 DP 解决方案,但是如何在小于线性的时间内增量更新这个问题并不明显。
相反,我们可以找到更容易更新的分而治之的 DP 解决方案。将范围分成两半,然后在每一半中再次解决问题。事实上,我们解决了该问题的四种变体,具体取决于是否允许使用左端点和/或右端点。鉴于这两部分的这些结果,我们可以轻松地将它们组合起来以形成整个范围的结果。这些范围在谷仓上形成一棵分段树,其中每个树节点都依赖于它的两个子节点,而答案保存在树的根部。
鉴于这种树结构,现在很清楚我们如何在 O(log N) 时间内进行增量更新:更新会影响树的叶子,而叶子又需要更新其 O(log N) 的祖先。
这个解决方案没有代码,我也不是很明白如何实现这个想法。如果有人能更彻底地解释它或告诉我如何编码,我将不胜感激。
编辑:
我现在更好地理解解决方案所说的内容,并且我已经编写了解决方案。但是,该代码仅适用于前两个测试数据。我以为我编写了他们所说的内容,但我一定犯了一个错误。这是我的代码(Java):
import java.io.File;
import java.io.FileWriter;
import java.io.PrintWriter;
import java.util.Scanner;
/**
* Created by jameslennon on 1/19/14.
*/
public class optmilk {
static int n, d;
static int[] m, indx;
static node[] tree;
public static void main(String[] args) throws Exception {
Scanner in = new Scanner(new File("optmilk.in"));
PrintWriter out = new PrintWriter(new FileWriter("optmilk.out"));
n = in.nextInt();
m = new int[n];
indx = new int[n];
d = in.nextInt();
tree = new node[1 << n + 1];
for (int i = 0; i < n; i++) {
m[i] = in.nextInt();
}
construct(1, 0, n - 1);
int r = 0;
for (int i = 0; i < d; i++) {
int a = in.nextInt() - 1, b = in.nextInt();
update(a, b);
m[a] = b;
r += max(tree[1].none, tree[1].both, tree[1].left, tree[1].right);
}
//System.out.println(r);
out.println(r);
out.close();
System.exit(0);
}
private static void update(int a, int b) {
int i = indx[a];
int w = 1;
tree[i].both = b;
i /= 2;
while (true) {
update_node(i, w);
w *= 2;
if (i == 1) break;
i /= 2;
}
}
private static int max(int... a) {
int max = Integer.MIN_VALUE;
for (int b : a) {
max = Math.max(max, b);
}
return max;
}
private static void update_node(int i, int w) {
if (w == 1) {
tree[i].left = tree[2 * i].both;
tree[i].right = tree[2 * i + 1].both;
} else {
tree[i].none = max(tree[2 * i].none + tree[2 * i + 1].none, tree[2 * i].right + tree[2 * i + 1].none, tree[2 * i].none + tree[2 * i + 1].left);
tree[i].right = max(tree[2 * i].none + tree[2 * i + 1].right, tree[2 * i].right + tree[2 * i + 1].right, tree[2 * i].none + tree[2 * i + 1].both);
tree[i].left = max(tree[2 * i].left + tree[2 * i + 1].none, tree[2 * i].both + tree[2 * i + 1].none, tree[2 * i].left + tree[2 * i + 1].left);
tree[i].both = max(tree[2 * i].left + tree[2 * i + 1].right, tree[2 * i].both + tree[2 * i + 1].right, tree[2 * i].left + tree[2 * i + 1].both);
}
}
private static void construct(int i, int a, int b) {
if (b - a == 0) {
indx[a] = i;
tree[i] = new node(0, 0, 0, m[a]);
return;
}
construct(2 * i, a, (a + b) / 2);
construct(2 * i + 1, (a + b) / 2 + 1, b);
int both = max(tree[2 * i].left + tree[2 * i + 1].right, tree[2 * i].both + tree[2 * i + 1].right, tree[2 * i].left + tree[2 * i + 1].both);
if (b - a == 1) both = 0;
tree[i] = new node(max(tree[2 * i].none + tree[2 * i + 1].none, tree[2 * i].right + tree[2 * i + 1].none, tree[2 * i].none + tree[2 * i + 1].left),
max(tree[2 * i].none + tree[2 * i + 1].right, tree[2 * i].right + tree[2 * i + 1].right, tree[2 * i].none + tree[2 * i + 1].both),
max(tree[2 * i].left + tree[2 * i + 1].none, tree[2 * i].both + tree[2 * i + 1].none, tree[2 * i].left + tree[2 * i + 1].left),
both);
}
static class node {
int none, right, left, both;
public node(int n, int r, int l, int b) {
none = n;
right = r;
left = l;
both = b;
}
}
}
【问题讨论】:
-
这将是相当多的代码。你了解线段树的概念吗?这就是我首先要看的。
标签: algorithm tree dynamic-programming