初见安~这里是传送门:洛谷P2680 运输计划
【懒到选择截屏】
题解
一眼做不来【啪。】
所求是路径最大值最小化。看到这种问法一眼二分,但是似乎二分并不好走,难点就在于让哪条边的边权为0,也就是check函数怎么写。
那我们不妨把重心转换一下。再回到二分的思路上来,我们二分答案,也就是最长的路径长度,如果有路径的长度大于我们二分的limit,那么我们要化为0的边必然在这条路径上。换言之,我们要选择的边就是 众多长度大于limit的路径 的公共边中的一条。
很显然,我们当然是在公共边里面选最长的那一条了。
如果公共边最长的一条的长度都小于最长的拿一条路径和limit的差值,也就是说就算把这条最优的边给赋值成0也做不到在limit以内,不符合要求。反之,符合。
所以这个题说白了就是二分答案,然后标记公共边,找最长的一个,check。
再细化,我们怎么找公共边?
假设我们有tot条路径长度超过了limit,我们给这tot条路径走过的边都标记一次,那么我们看图上哪些边被走过了tot次,就是这tot条边的公共边了。路径覆盖,求次数——这不就是树上差分了嘛~~边化点差分一下,就可以找到所有的公共边了。
总结——二分枚举答案, 判定每条路径的长度是否在limit以内,并差分标记,遍历差分内容找公共边,复杂度完全可行。
上代码——【求路径长度的部分,因为用树剖求LCA但是又不想写线段树所以强行又差分了一下求dis
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<queue>
#define maxn 300005
using namespace std;
typedef long long ll;
int read() {
int x = 0, f = 1, ch = getchar();
while(!isdigit(ch)) {if(ch == '-') f = -1; ch = getchar();}
while(isdigit(ch)) x = (x << 1) + (x << 3) + ch - '0', ch = getchar();
return x * f;
}
struct edge {int to, w, nxt;} e[maxn << 1];
int head[maxn], k = 0;
void add(int u, int v, int w) {e[k] = {v, w, head[u]}; head[u] = k++;}
int n, m;
int fa[maxn], dep[maxn], size[maxn], son[maxn], top[maxn], dis[maxn];
struct HLD {//封装,树剖求LCA
void dfs1(int u) {
size[u] = 1;
for(int i = head[u]; ~i; i = e[i].nxt) {
register int v = e[i].to; if(v == fa[u]) continue;
fa[v] = u; dep[v] = dep[u] + 1; dis[v] = dis[u] + e[i].w; //printf("fa[%d] = %d\n", v, u);
dfs1(v); size[u] += size[v];
if(size[v] > size[son[u]]) son[u] = v;
}
}
void dfs2(int u, int tp) {
top[u] = tp;
if(son[u]) dfs2(son[u], tp);
for(int i = head[u]; ~i; i = e[i].nxt) {
register int v = e[i].to;
if(v != fa[u] && v != son[u]) dfs2(v, v);
}
}
int LCA(int u, int v) {
while(top[u] != top[v]) {
if(dep[top[u]] > dep[top[v]]) swap(u, v);
v = fa[top[v]];
}
if(dep[u] > dep[v]) return v; else return u;
}
}H;
int qu[maxn], qv[maxn], lca[maxn], len[maxn], cnt[maxn], max_num;
void solve(int u, int tot) {
for(int i = head[u]; ~i; i = e[i].nxt) {
register int v = e[i].to; if(v == fa[u]) continue;
solve(v, tot);
if(cnt[v] == tot) max_num = max(max_num, e[i].w);//这是一条公共边了
cnt[u] += cnt[v];//树上差分统计过程
}
}
bool check(int lim) {
memset(cnt, 0, sizeof cnt);
register int tot = 0, max_len = 0; max_num = 0;
for(int i = 1; i <= m; i++) if(len[i] > lim) {
max_len = max(max_len, len[i] - lim); tot++;//找最大差值
cnt[qu[i]]++, cnt[qv[i]]++, cnt[lca[i]] -= 2;//差分标记边的覆盖次数【边化点
}
solve(1, tot);
if(max_num >= max_len) return true;//如果最长公共边都不能消除最大差值,就不行
else return false;
}
signed main() {
memset(head, -1, sizeof head);
n = read(), m = read();
for(int i = 1, u, v, w; i < n; i++) u = read(), v = read(), w = read(), add(u, v, w), add(v, u, w);
H.dfs1(1); H.dfs2(1, 1);
for(int u, v, i = 1; i <= m; i++) {
u = read(), v = read();
qu[i] = u, qv[i] = v;
lca[i] = H.LCA(u, v);
len[i] = dis[u] + dis[v] - 2 * dis[lca[i]];//dis差分,求len——路径长度
}
register int l = 0, r = 5e8, mid, ans;// r的上界是3e8
while(l <= r) {
mid = l + r >> 1;
if(check(mid)) r = mid - 1, ans = mid;
else l = mid + 1;
}
printf("%d\n", ans);
return 0;
}
迎评:)
——End——