清华集训2016-汽水
试题描述:
牛牛来到了一个盛产汽水的国度旅行。
这个国度的地图上有n个城市,这些城市之间用 n−1 条道路连接,任意两个城市之间,都存在一条路径连接。这些城市生产的汽水有许多不同的风味,在经过道路 i 时,牛牛会喝掉 wi 的汽水。牛牛非常喜欢喝汽水,但过量地用汽水是有害健康的,因此,他希望在他旅行的这段时间内,平均每天喝到的汽水的量尽可能地接近给定的一个正整数 k。
同时,牛牛希望他的旅行计划尽可能地有趣,牛牛会先选择一个城市作为起点,然后每天通过一条道路,前往一个没有去过的城市,最终选择在某一个城市结束旅行。
牛牛还要忙着去喝可乐,他希望你帮他设计出一个旅行计划,满足每天 |平均每天喝到的汽水−k|的值尽量小,请你告诉他这个最小值。
输入
第一行两个正整数 n,k。
接下来 n−1
行,每行三个正整数 ui,vi,wi,表示城市 ui 和城市 vi 之间有一条长度为 wi
的道路连接。
同一行相邻的两个整数均用一个空格隔开。
输出
一行一个整数,表示 |平均每天喝到的汽水−k| 的最小值的整数部分,即你只要将这个最小值向下取整然后输出即可。
样例输入
5 21
1 2 9
1 3 27
1 4 3
1 5 12
样例输出
1
solution:
简单的分数规划&点分治
不知道为什么考场上写的常数那么大,考完了又写了一遍跑得飞快(雾
看到最小化平均值,就应该尝试一下分数规划,也就是二分答案的做法
而因为要大规模处理链上的内容,应该要想到点分治
考虑这题怎么做
读入长度的时候先把所有长度减去k,这样最后求出来的一条链的长度的平均数就是答案
设点分治时候一棵子树的某一条链的有这样两个信息(A,B)
A表示该点到点分治的根的真实距离
B表示该点到点分治的根的深度
二分mid时,合并两条链(A1,B1) (A2,B2)有:
因为要取整的原因这里用<,到后面输出答案直接减一即可
因为B1+B2始终大于零 所以考虑只A1+A2。
A1+A2>0时有 $$A1-B1mid < B2mid-A2$$ 发现对A排序之后可以单调维护后面一坨的最大或最小值
小于0同理,于是这题就oo了
复杂度:$$O(nlog nlog v)$$
#include<bits/stdc++.h>
#define ll long long
#define R register
#define inf_int 2003060400
#define inf_ll 1000000000000000000
using namespace std;
namespace IO
{
template<class T>
void rea(T &x)
{
char ch=getchar();int f(0);x = 0;
while(!isdigit(ch)) {f|=ch=='-';ch=getchar();}
while(isdigit(ch)) {x=(x<<3)+(x<<1)+(ch^48);ch=getchar();}
x = f?-x:x;
}
template<class T>
T max(T a, T b) {return (a>b?a:b);}
template<class T>
T min(T a, T b) {return (a<b?a:b);}
}
using IO::rea;
const int N = 500005;
int n;
ll ans = inf_ll, k;
struct EDGE{int to, nex; ll w;}e[N];
int head[N], cnt;
void add(int x, int y, ll w)
{
e[++cnt].to = y, e[cnt].nex = head[x];
e[cnt].w = w, head[x] = cnt;
}
bool vis[N], flag;
int siz[N], root, Max, S;
namespace solveLink
{
int tot;
struct node
{
ll A, B; int anc;
bool operator < (const node b) { return A < b.A; }
} len[N];
void getlen(int x, int fa, ll A, ll B, int anc)
{
len[++tot].A = A, len[tot].B = B, len[tot].anc = anc;
for(R int i = head[x]; i; i = e[i].nex)
{
if(e[i].to == fa || vis[e[i].to]) continue;
getlen(e[i].to, x, A+e[i].w, B+1, (x==fa?e[i].to:anc));
}
}
bool update(node nod, ll B, node &a, node &b, ll fg, bool upd)
{
if(a.anc == nod.anc) if(fg*B*-1ll < fg*b.B) return 1; else;
else if(fg*B*-1ll < fg*a.B) return 1; else;
if(!upd) return 0;
if(fg*B >= fg*a.B)
{
if(a.anc != nod.anc) b = a;
a = nod; a.B = B;
}
else if(fg*B >= fg*b.B && nod.anc != a.anc) b = nod, b.B = B;
return 0;
}
bool check1(ll x)
{
int p = tot;
node mx, mx1; mx.A = mx1.A = mx.B = mx1.B = -inf_ll;
for(R int i = 1; i <= tot && i < p; ++i)
{
while(len[p].A+len[i].A >= 0 && p > i)
{ if(update(len[p], len[p].B*x-len[p].A, mx, mx1, 1, 1)) return 1; p--; }
if(update(len[i], len[i].B*x-len[i].A, mx, mx1, 1, 0)) return 1;
}
return 0;
}
bool check2(ll x)
{
int p = 1;
node mn, mn1; mn.A = mn1.A = mn.B = mn1.B = inf_ll;
for(R int i = tot; i >= 1; --i)
{
while(len[p].A+len[i].A <= 0 && p < i)
{ if(update(len[p], -len[p].A-len[p].B*x, mn, mn1, -1, 1)) return 1; p++; }
if(update(len[i], -len[i].A-len[i].B*x, mn, mn1, -1, 0)) return 1;
}
return 0;
}
void Run(int x)
{
tot = 0; getlen(x, x, 0, 0, 0);
sort(len+1, len+1+tot);
ll l = 0, r = ans, mid;
while(l < r)
{
mid = l+r >> 1;
if(check1(mid) || check2(mid)) r = mid;
else l = mid+1;
}
ans = min(ans, l);
}
}
void Findrt(int x, int fa)
{
siz[x] = 1;
int maxson = 0;
for(R int i = head[x]; i; i = e[i].nex)
{
if(vis[e[i].to] || e[i].to == fa) continue;
Findrt(e[i].to, x);
siz[x] += siz[e[i].to];
maxson = max(maxson, siz[e[i].to]);
}
maxson = max(maxson, S-siz[x]);
if(maxson < Max) Max = maxson, root = x;
}
void Run(int x)
{
vis[x] = 1;
solveLink::Run(x);
int tots = S;
for(R int i = head[x]; i; i = e[i].nex)
{
if(vis[e[i].to]) continue;
if(siz[e[i].to] > siz[x]) S = tots-siz[x];
else S = siz[e[i].to];
Max = inf_int;
Findrt(e[i].to, x);
Run(root);
}
}
void init()
{
rea(n), rea(k);
for(R int i = 1; i < n; ++i)
{
int x, y; ll w; rea(x), rea(y), rea(w);
add(x, y, w-k), add(y, x, w-k);
}
S = n, Max = inf_int;
Findrt(1, 0);
Run(root);
printf("%lld
", ans-1);
}
int main()
{
freopen("soda.in","r",stdin);
freopen("soda.out","w",stdout);
using IO::max;using IO::min; init();
return 0;
}