[UOJ#276]【清华集训2016】汽水

试题描述

牛牛来到了一个盛产汽水的国度旅行。

这个国度的地图上有 \(n\) 个城市,这些城市之间用 \(n−1\) 条道路连接,任意两个城市之间,都存在一条路径连接。这些城市生产的汽水有许多不同的风味,在经过道路 \(i\) 时,牛牛会喝掉 \(w_i\) 的汽水。牛牛非常喜欢喝汽水,但过量地饮用汽水是有害健康的,因此,他希望在他旅行的这段时间内,平均每天喝到的汽水的量尽可能地接近给定的一个正整数 \(k\)。

同时,牛牛希望他的旅行计划尽可能地有趣,牛牛会先选择一个城市作为起点,然后每天通过一条道路,前往一个没有去过的城市,最终选择在某一个城市结束旅行。

牛牛还要忙着去喝可乐,他希望你帮他设计出一个旅行计划,满足每天 \(|平均每天喝到的汽水−k|\) 的值尽量小,请你告诉他这个最小值。

输入

第一行两个正整数 \(n,k\) 。

接下来 \(n−1\) 行,每行三个正整数 \(u_i,v_i,w_i\),表示城市 \(u_i\) 和城市 \(v_i\) 之间有一条长度为 \(w_i\) 的道路连接。

同一行相邻的两个整数均用一个空格隔开。

输出

一行一个整数,表示 \(|平均每天喝到的汽水−k|\) 的最小值的整数部分,即你只要将这个最小值向下取整然后输出即可。

输入示例

5 21
1 2 9
1 3 27
1 4 3
1 5 12

输出示例

1

数据规模及约定

对于 \(20\texttt{%}\) 的数据,\(n \le 1000\)。

对于另外 \(20\texttt{%}\) 的数据,保证编号为 \(i(1 \le i \le n−1)\) 的节点和编号为 \(i+1\) 的节点之间连接了一条边。

对于另外 \(20\texttt{%}\) 的数据,保证数据是以 \(1\) 为根的完全二叉树(在完全二叉树中,节点 \(i(2 \le i \le n)\) 和节点 \(\lfloor i \div 2 \rfloor\) 之间有一条道路)。

对于另外 \(20\texttt{%}\) 的数据,保证除节点 \(1\) 以外,其他节点和节点 \(1\) 之间都有一条道路。

对于 \(100\texttt{%}\) 的数据,\(1 \le n \le 5 \times 10^4,0 \le w_i \le 10^{13},0 \le k \le 10^{13}\)。

题解

我是垫底小王子!!!

看到最小化平均值相关的东西,首先尝试分数规划(即二分答案)。

假设当前二分的答案是 \(x\),那么就需要验证是否存在一条路径 \(S\) 使得 \(|\frac {\sum_{i \in S} {w_i}} {t} - k| < x\)。展开绝对值得到 \(-x < \frac {\sum_{i \in S} {w_i}} {t} - k < x\)。

下面令 \(t = |S|\)。

先看前半部分 \(\frac {\sum_{i \in S} {w_i}} {t} - k > -x\),两边同乘 \(t\) 可以导出 \(\sum_{i \in S} {w_i} > t(k - x)\),然后移项(常规套路),得到 \(\sum_{i \in S} {w_i - k + x} > 0\)。

后半部分同理 \(\frac {\sum_{i \in S} {w_i}} {t} - k < x\) \(\Rightarrow\) \(\sum_{i \in S} {w_i} < t(k + x)\) \(\Rightarrow\) \(\sum_{i \in S} {w_i - k - x} < 0\)。

然后因为是要查找存不存在这样的“链”,我们点分治。直接想跨重心的部分吧,将边权分别改成 \(w_i - k - x\) 和 \(w_i - k + x\) 然后 dfs 出深度,令 \(A_l\) 表示某个已经处理过的子树中的节点的采用第一种边权的深度,\(B_l\) 已经处理过的表示采用第二种边权的深度,\(A_r\) 表示待处理的第一种边权深度,\(B_r\) 表示待处理的第二种边权深度,那么需要满足 \(A_r < -A_l\) 且 \(B_r > -B_l\),这样我们可以将所有数对 \((-A_l, -B_l)\) 以 \(-A_l\) 为关键字放到 Treap 中,维护 \(-B_l\) 的最小值即可,若所有关键字大于 \(A_r\) 的数对中 \(-B_l\) 的最小值小于 \(B_r\),则表示当前二分的答案可行。

二分可以当前答案为上界,卡卡常数。

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <algorithm>
using namespace std;
#define LL long long LL read() {
LL x = 0, f = 1; char c = getchar();
while(!isdigit(c)){ if(c == '-') f = -1; c = getchar(); }
while(isdigit(c)){ x = x * 10 + c - '0'; c = getchar(); }
return x * f;
} #define maxn 50010
#define maxm 100010
#define ool (1ll << 60) int n, m, head[maxn], nxt[maxm], to[maxm];
LL K, val[maxm]; void AddEdge(int a, int b, LL c) {
to[++m] = b; val[m] = c; nxt[m] = head[a]; head[a] = m;
swap(a, b);
to[++m] = b; val[m] = c; nxt[m] = head[a]; head[a] = m;
return ;
} struct pll {
LL A, B;
pll() {}
pll(LL _, LL __): A(_), B(__) {}
} ;
struct Node {
LL A, B, mn; int r;
Node() {}
Node(LL _, LL __): A(_), B(__), r(rand()) {}
bool operator < (const Node& t) const { return A < t.A; }
} ;
struct Treap {
int rt, ToT, ch[maxn][2], fa[maxn];
Node ns[maxn]; void Clear(int& o) {
if(!o) return ;
Clear(ch[o][0]); Clear(ch[o][1]);
fa[o] = 0; o = 0;
return ;
}
void clear() {
Clear(rt);
ToT = 0;
return ;
} void maintain(int o) {
ns[o].mn = ns[o].B;
if(ch[o][0]) ns[o].mn = min(ns[o].mn, ns[ch[o][0]].mn);
if(ch[o][1]) ns[o].mn = min(ns[o].mn, ns[ch[o][1]].mn);
return ;
} void rotate(int u) {
int y = fa[u], z = fa[y], l = 0, r = 1;
if(z) ch[z][ch[z][1]==y] = u;
if(ch[y][1] == u) swap(l, r);
fa[u] = z; fa[y] = u; fa[ch[u][r]] = y;
ch[y][l] = ch[u][r]; ch[u][r] = y;
maintain(y); maintain(u);
return ;
}
void insert(int& o, Node v) {
if(!o) {
ns[o = ++ToT] = v;
return maintain(o);
}
bool d = ns[o] < v;
insert(ch[o][d], v); fa[ch[o][d]] = o;
if(ns[ch[o][d]].r > ns[o].r) {
int t = ch[o][d];
rotate(t); o = t;
}
return maintain(o);
} LL qlarger(int o, LL lim, LL smaller_than_this) {
if(!o) return ool;
LL rmn = ch[o][1] ? ns[ch[o][1]].mn : ool;
if(ns[o].A <= lim) return qlarger(ch[o][1], lim, smaller_than_this);
else {
if(min(rmn, ns[o].B) < smaller_than_this) return smaller_than_this - 1;
return qlarger(ch[o][0], lim, smaller_than_this);
}
}
} sol; int rt, size, f[maxn], siz[maxn];
bool vis[maxn];
void getrt(int u, int fa) {
siz[u] = 1; f[u] = 0;
for(int e = head[u]; e; e = nxt[e]) if(to[e] != fa && !vis[to[e]]) {
getrt(to[e], u);
siz[u] += siz[to[e]];
f[u] = max(f[u], siz[to[e]]);
}
f[u] = max(f[u], size - siz[u]);
if(f[rt] > f[u]) rt = u;
return ;
}
pll now[maxn];
int cnow;
void dfs(int u, int fa, LL A, LL B, LL x) {
now[++cnow] = pll(A, B);
siz[u] = 1;
for(int e = head[u]; e; e = nxt[e]) if(!vis[to[e]] && to[e] != fa)
dfs(to[e], u, A + val[e] - K - x, B + val[e] - K + x, x), siz[u] += siz[to[e]];
return ;
}
bool check(int u, LL x) {
sol.clear();
for(int e = head[u]; e; e = nxt[e]) if(!vis[to[e]]) {
cnow = 0;
dfs(to[e], u, val[e] - K - x, val[e] - K + x, x);
for(int i = 1; i <= cnow; i++) {
if(now[i].A < 0 && now[i].B > 0) return 1;
if(now[i].B > sol.qlarger(sol.rt, now[i].A, now[i].B)) return 1;
}
for(int i = 1; i <= cnow; i++) sol.insert(sol.rt, Node(-now[i].A, -now[i].B));
}
return 0;
}
LL ans;
void solve(int u) {
vis[u] = 1;
LL l = 0, r = ans;
while(l < r) {
LL mid = l + r >> 1;
if(check(u, mid)) r = mid; else l = mid + 1;
}
ans = l;
if(!ans) return ;
for(int e = head[u]; e; e = nxt[e]) if(!vis[to[e]]) {
f[rt = 0] = size = siz[to[e]]; getrt(to[e], u);
solve(rt);
}
return ;
} int main() {
n = read(); K = read();
for(int i = 1; i < n; i++) {
int a = read(), b = read(); LL c = read();
AddEdge(a, b, c);
} ans = (LL)1e13 + 1;
f[rt = 0] = size = n; getrt(1, 0);
solve(rt); printf("%lld\n", ans - 1); return 0;
}

[UOJ#276]【清华集训2016】汽水的更多相关文章

  1. [UOJ#276][清华集训2016]汽水[分数规划+点分治]

    题意 给定一棵 \(n\) 个点的树,给定 \(k\) ,求 \(|\frac{\sum w(路径长度)}{t(路径边数)}-k|\)的最小值. \(n\leq 5\times 10^5,k\leq ...

  2. [UOJ#274][清华集训2016]温暖会指引我们前行

    [UOJ#274][清华集训2016]温暖会指引我们前行 试题描述 寒冬又一次肆虐了北国大地 无情的北风穿透了人们御寒的衣物 可怜虫们在冬夜中发出无助的哀嚎 “冻死宝宝了!” 这时 远处的天边出现了一 ...

  3. BZOJ.4738.[清华集训2016]汽水(点分治 分数规划)

    BZOJ UOJ 记\(val_i\)是每条边的边权,\(s\)是边权和,\(t\)是经过边数,\(k\)是给定的\(k\). 在点分治的时候二分答案\(x\),设\(|\frac st-k|=x\) ...

  4. BZOJ 4732 UOJ #268 [清华集训2016]数据交互 (树链剖分、线段树)

    题目链接 (BZOJ) https://www.lydsy.com/JudgeOnline/problem.php?id=4732 (UOJ) http://uoj.ac/problem/268 题解 ...

  5. UOJ276 [清华集训2016] 汽水 【二分答案】【点分治】【树状数组】

    题目分析: 这种乱七八糟的题目一看就是点分治,答案有单调性,所以还可以二分答案. 我们每次二分的时候考虑答案会不会大于等于某个值,注意到系数$k$是无意义的,因为我们可以通过转化使得$k=0$. 合并 ...

  6. 并不对劲的uoj276. [清华集训2016]汽水

    想要很对劲的讲解,请点击这里 题目大意 有一棵\(n\)(\(n\leq 50000\))个节点的树,有边权 求一条路径使该路径的边权平均值最接近给出的一个数\(k\) 输出边权平均值下取整的整数部分 ...

  7. UOJ 275. 【清华集训2016】组合数问题

    UOJ 275. [清华集训2016]组合数问题 组合数 $C_n^m $表示的是从 \(n\) 个物品中选出 \(m\) 个物品的方案数.举个例子,从$ (1,2,3)(1,2,3)$ 三个物品中选 ...

  8. UOJ #269. 【清华集训2016】如何优雅地求和

    UOJ #269. [清华集训2016]如何优雅地求和 题目链接 给定一个\(m\)次多项式\(f(x)\)的\(m+1\)个点值:\(f(0)\)到\(f(m)\). 然后求: \[ Q(f,n,x ...

  9. UOJ #274. 【清华集训2016】温暖会指引我们前行 [lct]

    #274. [清华集训2016]温暖会指引我们前行 题意比较巧妙 裸lct维护最大生成树 #include <iostream> #include <cstdio> #incl ...

随机推荐

  1. js 获取当前年月日时分秒星期

    $("#aa").click(function () { var date = new Date(); this.year = date.getFullYear(); this.m ...

  2. python_87_shelve模块

    'shelve模块是一个简单的key,value将内存数据通过文件持久化的模块,可以持久化任何pickle可支持的python数据格式(只支持pickle)' #序列化,将数据写入文件 import ...

  3. js生成指定范围内随机数

    其现方法的核心是JavaScript的Math对象.代码如下: <!DOCTYPE html> <html lang="en"> <head> ...

  4. 10分钟搞懂toString和valueOf函数(详细版)

    首先要说明的是这两种方法是toPrimitive抽象操作里会经常用到的. 默认情况下,执行这个抽象操作时会先执行valueOf方法,如果返回的不是原始值,会继续执行toString方法,如果返回的还不 ...

  5. 11.使用while和for循环分别打印字符串s=’asdfer’中每个元素

    1).for循环 s = 'asdfer' for i in s: print(i) 2).while循环 s = 'asdfer' while 1: print(s[index]) index += ...

  6. Java中的后台线程和join方法

    /*守护线程(后台线程):在一个进程中如果只剩下 了守护线程,那么守护线程也会死亡. 需求: 模拟QQ下载更新包. 一个线程默认都不是守护线程. */ public class Demo extend ...

  7. ☆☆☆Dojo中define和declare的结合使用

    在原生的js中是不可以创建类的,没有class这个关键字,但是在dojo中,dojo自定义了一个模块叫做dojo/_base/declare,用这个模块我们可以创建自己的类,实现面向对象编程. 单继承 ...

  8. 因 URL 意外地以“/HelloWorld”结束,请求格式无法识别。

    web.config文件中的 <system.web> 节点下加入:<webServices>    <protocols>        <add name ...

  9. 01_11_Strtus2简单数据验证

    01_11_Strtus2简单数据验证 1. 引入struts标签 <%@taglib uri="/struts-tags" prefix="s" %&g ...

  10. 【dp】P1077 摆花

    基础DP题 题目描述 小明的花店新开张,为了吸引顾客,他想在花店的门口摆上一排花,共m盆.通过调查顾客的喜好,小明列出了顾客最喜欢的n种花,从1到n标号.为了在门口展出更多种花,规定第i种花不能超过a ...