给定一棵树, 你可以在树的直径上确定一条长度不超过 \(S\) 的链, 使得树上离此链最长的点距离最小, 输出这个距离
P2491 数据范围为 P1099 的 \(1000\) 倍
Solution
首先两次 \(dfs\) 确定树的直径, 即第一次随意从某一点出发到达最远点记为 \(s\), 第二次从 \(s\) 出发到达最远点 \(t\) , 则 \(s-t\) 即为树的直径
现在我们得到了直径, 试想树上现在有一条链, 包含点 \(a_{1},a_{2}...a_{n}\), 树上到此链最长的点无非是以下三个的最大值:
- \(a_{1}\) 到直径首的长度
- \(a_{t}\) 到直径末的长度
- \(Max_{i = 1}^{t}d_{i}\), \(d_{i}\) 为点 \(i\) 在不经过直径能到达最远点的长度
通过树的性质可得: 链上最远点长度必然包括直径到最远点的长度(才能保证最优)
所以实际上我们只需要维护在直径上长度不超过 \(S\) 的链的 \(1, 2\) 两点, 与直径最长取最大值即可
贪心可得在保证链长 \(<=S\) 的情况下越长越优
所以维护两个指针 \(l, r\), 维护链上的左右端点
枚举每一个右端点同时保持链长 \(<= S\) ,缩短左端点更新答案即可
此题细节较多,看注释
Code(P2491)
#include<iostream>
#include<cstdio>
#include<queue>
#include<cstring>
#include<algorithm>
#include<vector>
#define LL long long
using namespace std;
int RD(){
int out = 0,flag = 1;char c = getchar();
while(c < '0' || c >'9'){if(c == '-')flag = -1;c = getchar();}
while(c >= '0' && c <= '9'){out = out * 10 + c - '0';c = getchar();}
return flag * out;
}
const int maxn = 600019,INF = 1e9 + 19;
int head[maxn],nume = 1;
struct Node{
int v,dis,nxt;
}E[maxn << 3];
void add(int u,int v,int dis){
E[++nume].nxt = head[u];
E[nume].v = v;
E[nume].dis = dis;
head[u] = nume;
}
int num, S;
int s, t;
int pre[maxn], dis[maxn], disl[maxn], disr[maxn], disnxt[maxn];//前驱点,非路径距离,链上距离,邻接距离
vector<int>G;
bool vis[maxn];
struct Fa{int p, dis;};
Fa get_longest(int u, int F, int tim){//函数多用
Fa ret = (Fa){u, 0};int last = u, dn = 0;
for(int i = head[u];i;i = E[i].nxt){
int v = E[i].v;
if(v == F || vis[v])continue;
Fa ansv = get_longest(v, u, tim);
if(ansv.dis + E[i].dis > ret.dis){
last = v, dn = E[i].dis;
ret.dis = ansv.dis + E[i].dis;
ret.p = ansv.p;
}
}
if(tim == 1)pre[u] = last, disnxt[u] = dn;
return ret;
}
int ans = INF;
struct Que{
int index, val;
}Q[maxn];
int get_min(){
int now = s, add = 0;
while(pre[now] != now){
G.push_back(now);//入链
dis[now] = get_longest(now, -1, 0).dis;//处理非链上最长路径
add = max(add, dis[now]);
now = pre[now];
}
G.push_back(now);
dis[now] = get_longest(now, -1, 0).dis;//处理终点
add = max(add, dis[now]);
int tot = 0;
for(int i = G.size() - 1;i >= 1;i--){
now = G[i];
disr[now] = tot;
tot += disnxt[G[i - 1]];
}
disr[s] = tot;//处理链上最长距离的另一半
int l = 0, r = 0, len = 0;
ans = max(add, disr[G[r]]);
while(r < G.size()){
len += disnxt[G[r]];r++;
while(len > S)len -= disnxt[G[l]], l++;
//printf("l=%d r=%d\n", G[l], G[r]);
now = max(disr[G[r]], disl[G[l]]);
now = max(now, add);
ans = min(ans, now);
}
ans = max(ans, add);
return ans;
}
int main(){
num = RD();S = RD();
for(int i = 1;i <= num - 1;i++){
int u = RD(), v = RD(), dis = RD();
add(u, v, dis), add(v, u, dis);
}
s = get_longest(1, -1, 0).p, t = get_longest(s, -1, 1).p;
int i = s, tot = 0;
while(pre[i] != i)vis[i] = 1, disl[i] = tot, tot += disnxt[i], i = pre[i];//s到t
vis[t] = 1, disl[t] = tot;//标记直径上的点, 处理链上最长路径
printf("%d\n", get_min());
return 0;
//for(int i = 1;i <= num;i++)if(vis[i])printf("dis[%d]=%d\n", i, dis[i]);
}