NOIP2015·洛谷·运输计划

初见安~这里是传送门:洛谷P2680 运输计划

【懒到选择截屏】

题解

一眼做不来【啪。】

所求是路径最大值最小化。看到这种问法一眼二分,但是似乎二分并不好走,难点就在于让哪条边的边权为0,也就是check函数怎么写

那我们不妨把重心转换一下。再回到二分的思路上来,我们二分答案,也就是最长的路径长度,如果有路径的长度大于我们二分的limit,那么我们要化为0的边必然在这条路径上。换言之,我们要选择的边就是 众多长度大于limit的路径 的公共边中的一条。

很显然,我们当然是在公共边里面选最长的那一条了。

如果公共边最长的一条的长度都小于最长的拿一条路径和limit的差值,也就是说就算把这条最优的边给赋值成0也做不到在limit以内,不符合要求。反之,符合。

所以这个题说白了就是二分答案,然后标记公共边,找最长的一个,check。

再细化,我们怎么找公共边?

假设我们有tot条路径长度超过了limit,我们给这tot条路径走过的边都标记一次,那么我们看图上哪些边被走过了tot次,就是这tot条边的公共边了。路径覆盖,求次数——这不就是树上差分了嘛~~边化点差分一下,就可以找到所有的公共边了。

总结——二分枚举答案, O(m)判定每条路径的长度是否在limit以内,并差分标记,O(n)遍历差分内容找公共边,复杂度完全可行。

上代码——【求路径长度的部分,因为用树剖求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——

发布了158 篇原创文章 · 获赞 23 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/qq_43326267/article/details/102808689
今日推荐