• 【ybt金牌导航5-2-3】【luogu P4292】重建计划


    重建计划

    题目链接:ybt金牌导航5-2-3 / luogu P4292

    题目大意

    要你在一棵树中找一个边个数在一个区间范围内的路径,使得这个路径边权的平均值最大。
    输出平均值。

    思路

    看到平均值最大,自然想到二分答案。
    然后权值减去二分值,就是要找长度在区间范围内的路径使得边权和为正。
    (其实这里就是一个 01 分数规划)

    然后看到有关路径长度的规定,自然想到点分治。
    那我们由于它是要统计是否合法,我们不能用容斥,而是要用这样的方式:
    你现在的子树的值跟你之前查询过的子树的值匹配,然后再把你现在子树的值放入你查询过的子树的值里面。

    那接着就是如何匹配了。
    那你考虑枚举一个的长度,那另一个的长度就是一个区间。
    想到线段树等数据结构,但发现会超时。
    然后你会发现区间的大小是不变的,而且它每次只会往左 / 往右移,自然想到滑动窗口,直接上单调栈。

    然后实现的时候有些要注意的地方,看看代码就知道了。

    代码

    #include<cstdio>
    #include<cstring>
    #include<iostream>
    #define INF 0x3f3f3f3f3f3f3f3f
    #define eps 1e-6
    
    using namespace std;
    
    struct node {
    	double x;
    	int to, nxt;
    }e[200001], e_[200001];
    int f[100001], sz[100001];
    int n, L, U, x, y, root, ROOT, sum, KK_;
    int le[100001], KK, fa[100001], le_[100001];
    int maxdeg, bef_deg, Q[100001];
    double mid, z, degu[100001], degv[100001];
    bool in[100001], yes;
    
    void add(int x, int y, double z) {
    	e[++KK] = (node){z, y, le[x]}; le[x] = KK;
    	e[++KK] = (node){z, x, le[y]}; le[y] = KK;
    }
    
    void add_(int x, int y) {
    	e_[++KK_] = (node){0, y, le_[x]}; le_[x] = KK_;
    }
    
    //建点分数 start
    void dfs_find_root(int now, int father) {
    	sz[now] = 1;
    	f[now] = 0;
    	for (int i = le[now]; i; i = e[i].nxt)
    		if (e[i].to != father && !in[e[i].to]) {
    			dfs_find_root(e[i].to, now);
    			sz[now] += sz[e[i].to];
    			f[now] = max(f[now], sz[e[i].to]);
    		}
    	f[now] = max(f[now], sum - sz[now]);
    	if (f[now] < f[root]) root = now;
    }
    
    void find_root(int now, int sz) {
    	root = 0;
    	sum = sz;
    	dfs_find_root(now, 0);
    }
    
    int get_size(int now, int father) {
    	int re = 1;
    	for (int i = le[now]; i; i = e[i].nxt)
    		if (e[i].to != father && !in[e[i].to])
    			re += get_size(e[i].to, now);
    	return re; 
    }
    
    void build_tree(int now) {
    	in[now] = 1;
    	for (int i = le[now]; i; i = e[i].nxt)
    		if (!in[e[i].to]) {
    			find_root(e[i].to, get_size(e[i].to, 0));
    			fa[root] = now;
    			add_(now, root);
    			build_tree(root);
    		}
    }
    //建点分数 end
    
    //找到处理点到其子树会有的路径
    void get_road(int now, int father, int deg, double dist) {
    	maxdeg = max(maxdeg, deg);
    	degv[deg] = max(degv[deg], dist);
    	for (int i = le[now]; i; i = e[i].nxt)
    		if (e[i].to != father && !in[e[i].to])
    			get_road(e[i].to, now, deg + 1, dist + e[i].x - mid);
    			//注意这个地方距离要减 mid
    }
    
    void work(int now) {
    	//这里是为了防止选到一个子树里面的两个点,我们先算一个子树内的点和之前枚举到的子树的点的组合,然后再把这个子树内的点放入之前枚举到的点组合中
    	for (int i = le[now]; i; i = e[i].nxt)
    		if (!in[e[i].to]) {
    			get_road(e[i].to, now, 1, e[i].x - mid);//这里也别忘了减 mid
    			int l = 1, r = 0;
    			int I = bef_deg;
    			for (int j = 1; j <= maxdeg; j++) {//单调队列
    				while (l <= r && Q[l] > U - j) l++;
    				while (I >= L - j && I >= 0) {
    					while (l <= r && degu[Q[r]] <= degu[I]) r--;
    					Q[++r] = I;
    					I--;
    				}
    				if (l <= r && degu[Q[l]] + degv[j] > 0) {
    					yes = 1;
    					break;//找到不要直接退出,要初始化
    				}
    			}
    			//放进去之前的
    			for (int j = 1; j <= maxdeg; j++)
    				degu[j] = max(degu[j], degv[j]), degv[j] = -INF;
    			bef_deg = max(bef_deg, maxdeg);
    			maxdeg = 0;
    			
    			if (yes) break;//这里也是,不要直接退出
    		}
    	
    	for (int i = 1; i <= bef_deg; i++) {
    		degu[i] = -INF;
    	}
    	bef_deg = 0;
    }
    
    void check(int now) {
    	in[now] = 1;
    	work(now);
    	
    	if (!yes) {
    		for (int i = le_[now]; i; i = e_[i].nxt) {//走点分树
    			check(e_[i].to);
    			if (yes) break;
    		}
    	}
    	
    	in[now] = 0;
    }
    
    bool check_bef(int now) {
    	yes = 0;
    	check(now);
    	return yes;
    }
    
    int main() {
    	scanf("%d %d %d", &n, &L, &U);
    	for (int i = 1; i < n; i++) {
    		scanf("%d %d %lf", &x, &y, &z);
    		add(x, y, z);
    	}
    	
    	f[0] = 2147483647;//预处理求出点分树
    	for (int i = 1; i <= n; i++)
    		degu[i] = degv[i] = -INF;
    	find_root(1, n);
    	ROOT = root;
    	build_tree(root);
    	
    	memset(in, 0, sizeof(in));
    	double l = 0, r = 1e6, ans;//二分答案
    	while (r - l >= eps) {
    		mid = (l + r) / 2;
    		if (check_bef(ROOT)) {
    			ans = mid;
    			l = mid + eps;
    		}
    		else r = mid - eps;
    	}
    	
    	printf("%.3lf", ans);
    	
    	return 0;
    }
    
  • 相关阅读:
    Linux利用crontab命令定时任务
    Linux利用scp命令上传下载文件
    Linux利用ftp命令上传下载文件
    cmd杀死占用端口号的Java进程
    布隆过滤器
    redis缓存穿透、缓存击穿、缓存雪崩
    redis的主从复制master/slaver
    Redis的发布订阅Pub/Sub
    博客园隐藏反对按钮,并简单装饰推荐按钮
    redis事务
  • 原文地址:https://www.cnblogs.com/Sakura-TJH/p/YBT_JPDH_5-2-3.html
Copyright © 2020-2023  润新知