CF1016F Road Projects 分析
题目概述
给你一棵树,定义一条路径 \(a\rightarrow b\) 表示从 \(a\) 到 \(b\) 的最短路。
这是一颗 \(n\) 个点的树。
现在有 \(q\) 个工程方案,他们互相独立并且修建一条长度为 \(l\) 的路(你自己选,不能重边)。
对于每个工程方案你需要使 \(1\rightarrow n\) 的最短路尽可能大。
分析
由于一开始是一棵树,我们不妨先把 \(1\rightarrow n\) 的路径找出来,不难得出答案一定不会比这个大。
那么把这一条路径看成一条链,链上每一个点挂这一些点。
注意到如果一个链上的点(除了两边连接的)子树超过 \(1\),那么显然我可以直接在这颗子树上面做文章使得他的最短路为一开始的。
那么现在的问题就简化成了一条链上每一个点最多下面挂一个点(后文称之为挂点),求让最短路尽可能大的方案。
我们考虑从 \(n\) 走到 \(1\),记 \(dis_i\) 表示从 \(1\) 开始走到这里所需要的代价。
那么我们现在想要的连边是三种:
- 挂点与挂点
- 链上的点与挂点
- 链上的点和链上的点
考虑到是不走中间一段,直接维护一个 \(-dis_x+w_x\) 的最大值即可,当然还有些细节,其中 \(w_x\) 是 \(x\) 这个点与挂点的长度。
这题目的关键就是这个链树,然后就做完了,不算特别难。
代码
时间复杂度 \(\mathcal{O}(n+m)\),常数可能微大。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <stdlib.h>
#include <algorithm>
#include <vector>
#define int long long
#define N 300005
using namespace std;
struct edge{
int v,w;
};
vector<edge> g[N];
int fa[N],dis[N],n,q;
bool vis[N];
void dfs(int cur) {
for (auto i : g[cur])
if (i.v != fa[cur]) fa[i.v] = cur,dis[i.v] = dis[cur] + i.w,dfs(i.v);
}
void getpath() {
int now = n;
while(now) vis[now] = 1,now = fa[now];
}
int w[N];
bool flag;
void dfs2(int cur,int fa) {
for (auto i : g[cur])
if (!vis[i.v] && i.v != fa) {
if (flag) {
for (;q--;) printf("%lld\n",dis[n]);
exit(0);
}
flag = 1;
w[cur] = i.w;
dfs2(i.v,cur);
}
}
void solve(){
int now = n;
int mx1 = -1e18,mx2 = -1e18,lst = -1e18,ans = -1e18;
while(now) {
if (w[now]) {
ans = max(ans,w[now] + dis[now] + max(mx1,mx2));
mx1 = max(mx1,w[now] - dis[now]);
}
else {
ans = max(ans,dis[now] + max(mx1,lst));
lst = mx2,mx2 = max(mx2,- dis[now]);
}
now = fa[now];
}
for (int x;q --;) scanf("%lld",&x),printf("%lld\n",min(dis[n],dis[n] + ans + x));
}
signed main(){
cin >> n >> q;
for (int i = 1,u,v,w;i < n;i ++) {
scanf("%lld%lld%lld",&u,&v,&w);
g[u].push_back({v,w}),g[v].push_back({u,w});
}
dfs(1),getpath();
for (int i = 1;i <= n;i ++)
if (vis[i]) flag = 0,dfs2(i,0);
solve();
return 0;
}

浙公网安备 33010602011771号