前言
这题鸽了有一段时间了,今天上午自己想了想,冒出了一个不错的思路,而且似乎和之前听做过的同学讲的不太一样。然后就开始敲了,编译完马上过样例。交上去WA,TLE,各种错误接连而至。一直到刚刚,才结束了半天多的Debug,发现是快读打挂了…(逃…)
这是一个极其惨痛的教训。
题意
给你有N个节点的树,每个点都有一个权值。有Q个询问,每次询问从一个点走到另一个点,问你走过节点的序列 中最大的 ,且 。
约定
分析
PART 1 转化
其实就是求一个序列中两个数一前一后的最大差值,注意到这个差值并非简单的绝对值,它是有顺序性的,大的必须在后面。
我们先不考虑树上的情况。对于一个线性的序列
,我 ubhynj们考虑它的差分序列
,则任意两数之差就是他们之间的差值求和。这样,我们对序列
要求的这个最大差值就转化成了序列
上的最大连续子段和。
这样,对于每次询问
,我们可以从
走到
再走到
,并求出这段序列的最大连续子段和。
PART 2 提高效率
我们发现上面的查询每次都是
的,那么整个算法就变成了
的,这样显然会超时。我们需要提高查询的效率。
平时遇到树上这种查询的问题,我们一般用倍增法,压缩信息,达到降低复杂度的目的。那么对于这题是否可行呢?比如我们维护一个倍增数组
表示从
开始到
的第
级父亲的最大连续子段和,但是我们会发现,我们合并的时候,这个最大连续子段和可能是独立在
到它的第
级父亲或者是它的第
级父亲到它的第
级父亲里的,也可能是这两段中的接在一起的,但是我们只维护这一个数组却丢失了这些信息。并且,如果
和
不在同一条链上,那么我们走到
还会拐一个弯,当我们从
走到
的时候,方向和我们的倍增数组反过来了。
因此,看起来这个做法不可做。
怎么可能呢?其实我们刚刚考虑合并的时候,已经找到了合并的正确方法。
我们还是来考虑一个线性序列
,我们假设已经求得了
以及
这两个子段的最大连续子段和,记这两个子序列为
。对于每个序列我们维护四个信息:
- :序列中所有数的和
- :以序列第一数为起始的最大连续子段和
- :以序列最后一个数为结尾的最大连续子段和
- :序列中的最大连续子段和
则我们可以得出:
这些式子很容易就能理解。
那么对于树上倍增的做法也是类似,只是情况会更多一些。转移的式子很好推出,这里就不多展开了。并且注意,当从
走到
的时候,方向是反的,因此我们维护这个
的时候还要将我们对点和点差分得到的边权正负反一反即可。
参考程序
// vjudge 241954 I
#include <cstdio>
#include <cstring>
#include <algorithm>
const int MAXN = 30005;
const int Lg_N = 20;
const int MAXM = 50005;
struct Edge {
int to, next;
} E[MAXN << 1];
int N, B[MAXN], M, Q, last[MAXN], tote, dep[MAXN], F[MAXN][Lg_N];
namespace MAX_Spanning_tree {
struct Relation {
int x, y, ai;
bool operator<(const Relation & r) const {
return ai > r.ai;
}
} R[MAXM];
int fa[MAXN];
inline int find(int x) { return fa[x] == x ? x : fa[x] = find(fa[x]); }
int Kruskal();
}
// something wrong with this
// 这是莫名打挂的一个快读,就是因为这个东西调了一天,但是问题还没有找出来,不删去,放作警示
/* namespace FastIO {
template <typename T>
inline int read(T & x) {
x = 0; register char ch = getchar();
for (; ch != EOF && (ch < '0' || ch > '9'); ch = getchar());
if (ch == EOF) return -1;
for (; ch >= '0' && ch <= '9'; x = (x << 3) + (x << 1) + (ch ^ '0'), ch = getchar());
}
template <typename T>
inline void write(T x) {
if (!x) return (void)(putchar('0'));
register int arr[20], len = 0;
for (; x; arr[len++] = x % 10, x /= 10);
while (len) putchar(arr[--len] ^ '0');
}
template <typename T>
inline void writeln(T x) {
write(x), putchar('\n');
}
} */
inline void add_edge(int u, int v) {
E[++tote].to = v, E[tote].next = last[u], last[u] = tote;
E[++tote].to = u, E[tote].next = last[v], last[v] = tote;
}
void solve();
void dfs(int u);
int query(int u, int v);
int main() {
while (scanf("%d", &N) == 1) solve();
return 0;
}
void solve() {
int i;
for (i = 1; i <= N; i++) scanf("%d", &B[i]);
scanf("%d", &M);
{
using MAX_Spanning_tree::R;
for (i = 0; i < M; i++) scanf("%d%d%d", &R[i].x, &R[i].y, &R[i].ai);
}
printf("%d\n", MAX_Spanning_tree::Kruskal()); // 先要求一个最大生成树
memset(F, 0, sizeof(F));
dep[1] = 0, dfs(1);
scanf("%d", &Q);
int x, y;
for (i = 0; i < Q; i++) {
scanf("%d%d", &x, &y);
printf("%d\n", query(x, y));
}
}
namespace MAX_Spanning_tree {
int Kruskal() {
int i;
tote = 0;
memset(last, 0, sizeof(last));
for (i = 1; i <= N; i++) fa[i] = i;
std::sort(R, R + M);
int cnt = 0, N1 = N - 1, fa1, fa2;
int res = 0;
for (i = 0; i < M; i++) {
fa1 = find(R[i].x), fa2 = find(R[i].y);
if (fa1 != fa2) {
fa[fa1] = fa2;
add_edge(R[i].x, R[i].y);
res += R[i].ai;
if (++cnt == N1) break;
}
}
return res;
}
}
int Al_sum[MAXN][Lg_N], Max_sum[MAXN][Lg_N][2][2], DP[MAXN][Lg_N][2];
void dfs(int u) {
using std::max;
int i, v;
for (i = 1; i < Lg_N && F[u][i - 1]; i++)
F[u][i] = F[F[u][i - 1]][i - 1],
Al_sum[u][i] = Al_sum[u][i - 1] + Al_sum[F[u][i - 1]][i - 1],
Max_sum[u][i][0][0] = max(Al_sum[u][i], max(Max_sum[u][i - 1][0][0], Al_sum[u][i - 1] + Max_sum[F[u][i - 1]][i - 1][0][0])),
Max_sum[u][i][0][1] = max(Al_sum[u][i], max(Max_sum[u][i - 1][0][1] + Al_sum[F[u][i - 1]][i - 1], Max_sum[F[u][i - 1]][i - 1][0][1])),
Max_sum[u][i][1][0] = max(-Al_sum[u][i], max(Max_sum[u][i - 1][1][0], Max_sum[F[u][i - 1]][i - 1][1][0] - Al_sum[u][i - 1])),
Max_sum[u][i][1][1] = max(-Al_sum[u][i], max(Max_sum[u][i - 1][1][1] - Al_sum[F[u][i - 1]][i - 1], Max_sum[F[u][i - 1]][i - 1][1][1])),
DP[u][i][0] = max(max(DP[u][i - 1][0], DP[F[u][i - 1]][i - 1][0]), Max_sum[u][i - 1][0][1] + Max_sum[F[u][i - 1]][i - 1][0][0]),
DP[u][i][1] = max(max(DP[u][i - 1][1], DP[F[u][i - 1]][i - 1][1]), Max_sum[u][i - 1][1][1] + Max_sum[F[u][i - 1]][i - 1][1][0]);
for (i = last[u]; i; i = E[i].next)
if (E[i].to != F[u][0]) {
dep[v = E[i].to] = dep[u] + 1, F[v][0] = u,
DP[v][0][0] = Max_sum[v][0][0][0] = Max_sum[v][0][0][1] = Al_sum[v][0] = B[u] - B[v],
DP[v][0][1] = Max_sum[v][0][1][0] = Max_sum[v][0][1][1] = -Al_sum[v][0];
dfs(v = E[i].to);
}
}
int query(int u, int v) {
using std::max;
int i, delta, res = 0, max1 = 0, max2 = 0;
if (dep[u] > dep[v]) {
for (i = 0, delta = dep[u] - dep[v]; i < Lg_N && 1 << i <= delta; i++)
if (delta >> i & 1) { // 询问时维护答案,类似维护倍增数组,不过需要记录的信息少一些
res = max(max(res, DP[u][i][0]), max1 + Max_sum[u][i][0][0]);
max1 = max(Max_sum[u][i][0][1], Al_sum[u][i] + max1);
u = F[u][i];
}
}
else {
for (i = 0, delta = dep[v] - dep[u]; i < Lg_N && 1 << i <= delta; i++)
if (delta >> i & 1) {
res = max(max(res, DP[v][i][1]), max2 + Max_sum[v][i][1][0]);
max2 = max(Max_sum[v][i][1][1], max2 - Al_sum[v][i]);
v = F[v][i];
}
}
if (u == v) return max(res, max1 + max2);
for (i = Lg_N - 1; i >= 0; i--)
if (F[u][i] != F[v][i])
res = max(max(res, max(DP[u][i][0], DP[v][i][1])), max(max1 + Max_sum[u][i][0][0], max2 + Max_sum[v][i][1][0])),
max1 = max(Max_sum[u][i][0][1], max1 + Al_sum[u][i]),
max2 = max(Max_sum[v][i][1][1], max2 - Al_sum[v][i]),
u = F[u][i], v = F[v][i];
res = max(max(res, max(DP[u][0][0], DP[v][0][1])), max(max1 + Max_sum[u][0][0][0], max2 + Max_sum[v][0][1][0])),
max1 = max(Max_sum[u][0][0][1], max1 + Al_sum[u][0]),
max2 = max(Max_sum[v][0][1][1], max2 - Al_sum[v][0]);
return max(res, max1 + max2);
}
总结
这道题目,其实分析起来思维难度不大,代码认认真真仔细码,也不需要调太久。就是这个快读,嗯…我也不知道为什么,慎用!慎用!