题目大意:给定一张地图,它是一棵n个节点的树。mm爱跑步,mm要跑n天,每次都从一个结点开始跑步,每次都要跑到最远的那个结点,两天跑的最远距离有个差值,现在要从这n天里去若干天使得这些天的差值都小于m,问怎么取使得天数最多?n <= 100万,m <= 1亿。
解题思路:树形DP + 线段树。虽然数据量很吓人,但经过分析发现这题就是几个算法的聚合体,没有特别难。
1、先用树形dp思想把每个点到其他某点的最远距离求出来。具体做法是先把树变成有根树,深搜一遍找出每个节点到叶子的最长距离,这样算出来的是向下的最远距离,但还有一个向上即向父亲的那条分支的距离未纳进来计算。之后,再搜一遍把向上的那条分支也算进来,深搜和广搜都可以做。代码写得比较清晰,可以看下代码。这一步其实就是Hdu 2196 Computer,之前做过那题觉得这步还不是很难。
2、上一步得到一个数组,现在要从这个数组里找出连续的一段序列最大值最小值之差小于m,并且长度尽量大。暴力枚举肯定超时,100万*100万的计算量,100s可以算完吧。只能考虑O(N)的算法,因为是找最长的一个序列,我们可以通过维护两个指针来完成这个计算过程,两个指针表示区间的开始和结束,如果这个区间差值=<m,那么区间尾可以下移,如果>m那么区间必须缩小一些,区间头向后移,这样就可以通过不断增大区间头使得这个区间差值<=m.
3、那我们怎么找区间中的最大值最下值呢?用线段树啊,很常规的单点查询线段树,还没更新操作。
测试数据:
10 3
1 1
2 1
3 1
4 1
5 1
6 1
7 1
8 1
9 1
4 6
1 3
1 2
2 1
3 1
1 1
1 3
6 1
1 1
2 1
3 1
1 1
5 1
5 17
3 4
1 1
3 1
1 1
7 13
4 4
1 2
1 6
4 4
3 5
5 4
4 17
1 3
2 1
2 3
5 4
3 1
1 2
1 3
3 4
4 11
1 3
1 1
1 1
7 1
1 6
6 1
5 3
3 4
1 4
3 5
代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX 1100000
#define INF 2147483647
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
struct node {
int v, len, sum; //sum表示这个分支的距离最大值
node *next;
} *head[MAX * 2], tree[MAX * 2];
int n, m, ptr, ans, left, right;
int dp[MAX], mmax[MAX * 4], mmin[MAX * 4];
void Initial() {
ptr = ans = 0;
memset(dp, 0, sizeof (dp));
memset(head, NULL, sizeof (head));
}
inline int min(int a,int b) {
return (a)<(b)?(a):(b);
}
inline int max(int a,int b) {
return (a)>(b)?(a):(b);
}
void AddEdge(int a, int b, int c) {
tree[ptr].v = b, tree[ptr].len = c, tree[ptr].sum = 0;
tree[ptr].next = head[a], head[a] = &tree[ptr++];
}
void CountDist(int s, int pa) {
//转换为以1为根的树后向下计算到叶子的最大距离
node *p = head[s];
while (p != NULL) {
if (p->v != pa) {
CountDist(p->v, s);
dp[s] = max(dp[s], dp[p->v] + p->len);
p->sum = dp[p->v] + p->len;
}
p = p->next;
}
}
void Tree_DP(int s, int pa) {
//更新s到pa分支的最远值,并计算dp[s]
node *p = head[pa];
int tpmax = 0, i, j;
while (p != NULL) {
//计算pa节点除s外的最大分支
if (p->v != s)
tpmax = max(tpmax, p->sum);
p = p->next;
}
p = head[s];
while (p != NULL) {
//这次是向上更新,计算这个节点到其他分支的最大距离
if (p->v == pa) {
p->sum = tpmax + p->len;
break;
}
p = p->next;
}
p = head[s];
while (p != NULL) {
//向下递归,并更新dp[s]
dp[s] = max(dp[s], p->sum);
if (p->v != pa) Tree_DP(p->v, s);
p = p->next;
}
}
void Push_Up(int rt) {
mmax[rt] = max(mmax[rt << 1], mmax[rt << 1 | 1]);
mmin[rt] = min(mmin[rt << 1], mmin[rt << 1 | 1]);
}
void Build_Tree(int l, int r, int rt) {
if (l == r) {
mmax[rt] = mmin[rt] = dp[l];
return;
}
int m = (l + r) >> 1;
Build_Tree(lson);
Build_Tree(rson);
Push_Up(rt);
}
int Query_Max(int L, int R, int l, int r, int rt) {
//
if (L <= l && r <= R) return mmax[rt];
int m = (l + r) >> 1, tp1 = 0;
if (L <= m) tp1 = max(tp1, Query_Max(L, R, lson));
if (m + 1 <= R) tp1 = max(tp1, Query_Max(L, R, rson));
return tp1;
}
int Query_Min(int L, int R, int l, int r, int rt) {
if (L <= l && r <= R) return mmin[rt];
int m = (l + r) >> 1, tp1 = INF;
if (L <= m) tp1 = min(tp1, Query_Min(L, R, lson));
if (m + 1 <= R) tp1 = min(tp1, Query_Min(L, R, rson));
return tp1;
}
int main()
{
int i, j, k, a, b, c;
int tpmax, tpmin;
while (scanf("%d%d", &n, &m) != EOF) {
Initial();
for (i = 2; i <= n; ++i) {
scanf("%d%d", &b, &c);
AddEdge(i, b, c), AddEdge(b, i, c);
}
CountDist(1, 0);
Tree_DP(1, 0);
Build_Tree(1, n, 1);
left = 1, right = 1;
tpmax = tpmin = dp[1];
while (left <= right && right <= n) {
if (tpmax - tpmin <= m) {
ans = max(ans, right - left + 1);
right++;
tpmax = max(tpmax,dp[right]);
tpmin = min(tpmin,dp[right]);
}
else {
left++;
tpmax = Query_Max(left, right, 1, n, 1);
tpmin = Query_Min(left, right, 1, n, 1);
}
if (n - left < ans) break;
}
printf("%d\n", ans);
}
}
本文ZeroClock原创,但可以转载,因为我们是兄弟。