[NOIP2015 提高组] 运输计划

运输计划

  传送门:NOIP 2015 运输计划

题目大意

  给定一棵树和 m m m 条路径,现在可以让你把某一条边的权值改为 0 0 0,使得这些路径长度的最大值最小。

算法分析

20 pts

20 p t s 20pts 20pts 非常简单啊,就是那几个 m = 1 m = 1 m=1 的点,直接 l c a lca lca 处理出这条路径的长度,然后删去最大边就好了。

100 pts

  考虑二分答案,现在的问题就是如何实现 c h e c k ( x ) check(x) check(x)

  考虑找到一条边,使得路径长度大于 x x x 的所有路径都有这条边,并且减去这条边的边权之后路径长度小于等于 x x x

  所以我们就先用 l c a lca lca 预处理出所有路径的长度,然后对于每一个 x x x,记录每条边有多少原长超过 x x x 的路径经过(树上差分,路径覆盖),再遍历一遍整棵树找到有没有满足条件的边就可以了。

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define in read()
#define MAXN 300300
#define MAXM 4 * MAXN
#define INFI (int)3e8

inline int read(){
	int x = 0; char c = getchar();
	while(c < '0' or c > '9') c = getchar();
	while('0' <= c and c <= '9'){
		x = x * 10 + c - '0'; c=  getchar();
	}
	return x;
}

int tot = 0;
int first[MAXN] = { 0 };
int   nxt[MAXM] = { 0 };
int    to[MAXM] = { 0 };
int value[MAXM] = { 0 };
inline void add(int x, int y, int weight){
	nxt[++tot] = first[x];
	first[x] = tot; to[tot] = y;
	value[tot] = weight;
}

int pre[MAXN] = { 0 };
int dis[MAXN] = { 0 };
int dep[MAXN] = { 0 };
int f[MAXN][32] = { 0 };
void prework(int x, int fa){
	dep[x] = dep[fa] + 1;
	for(int i = 0; i <= 30; i++) f[x][i + 1] = f[f[x][i]][i];
	for(int e = first[x]; e; e = nxt[e]){
		int y = to[e];
		if(y == fa) continue;
		f[y][0] = x; dis[y] = dis[x] + value[e]; pre[y] = value[e];
		prework(y, x);
	}
}

int LCA(int x, int y){
	if(dep[x] < dep[y]) swap(x, y);
	for(int i = 30; i >= 0; i--){
		if(dep[f[x][i]] >= dep[y]) x = f[x][i];
		if(x == y) return x;
	}
	for(int i = 30; i >= 0; i--)
		if(f[x][i] != f[y][i])
			x = f[x][i], y = f[y][i];
	return f[x][0];
}

int n = 0; int m = 0;
struct Tedge{
	int len;
	int x, y, lca;
	bool operator < (const Tedge &rhs) const { return len > rhs.len; }
}edge[MAXM];

int mx = 0; int cnt = 0;
int flag = 0;
int sum[MAXN] = { 0 };

int judge(int x, int fa, int cnt, int mx){
	int num = sum[x];
	for(int e = first[x]; e; e = nxt[e]){
		int y = to[e];
		if(y == fa) continue;
		num += judge(y, x, cnt, mx);
	}
	if(num >= cnt and pre[x] >= mx) flag = 1;
	return num;
}

int check(int x){
	cnt = mx = flag = 0; memset(sum, 0, sizeof(sum));
	for(int i = 1; i <= m; i++){
		if(edge[i].len > x){
			sum[edge[i].x]++, sum[edge[i].y]++, sum[edge[i].lca] -= 2;
			cnt++; mx = max(mx, edge[i].len);
		}
	}
	if(cnt == 0) return 1;
	int k = judge(1, 0, cnt, mx - x);
	return flag;
}

signed main(){
	n = in; m = in;
	for(int i = 1; i < n; i++){
		int x = in, y = in, w = in;
		add(x, y, w); add(y, x, w);
	} prework(1, 0);
//	for(int i = 1; i <= n; i++) cout << dis[i] << ' '; puts("");
//	for(int i = 1; i <= n; i++) cout << pre[i] << ' '; puts("");
	for(int i = 1; i <= m; i++){
		edge[i].x = in; edge[i].y = in;
		edge[i].lca = LCA(edge[i].x, edge[i].y);
		edge[i].len = dis[edge[i].x] + dis[edge[i].y] - 2 * dis[edge[i].lca];
	}
	int l = 0; int r = INFI;
	while(l < r){
		int mid = (l + r) >> 1;
		if(check(mid)) r = mid;
		else l = mid + 1;
	}
	cout << l << '\n';
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值