UOJ#276. 【清华集训2016】汽水 二分答案 点分治

原文链接https://www.cnblogs.com/zhouzhendong/p/UOJ276.html

题解

首先,读入的时候就将所有的 $w_i$ 减掉 $k$ 。

于是我们要求的就是平均值最接近 0 的。

直接点分治,然后得到一些一端为当前点分中心的路径,设 $a,b$ 为其中两条路径,设 $v_a,v_b$ 为路径的边权和,$t_a,t_b$ 为路径的边数。

二分一个答案,假设差别**小于** $A$。由于题目要求的是下取整,所以我们为了方便,设的是**小于** $A$ ,这样做,最终只需要把答案减一就好了。

那么,如果合并路径 $a,b$ 可以满足条件,那么就会满足:

$$\left|\cfrac{v_a+v_b}{t_a+t_b}\right|<A\\|v_a+v_b|<A(t_a+t_b)\\=\begin{cases}v_a-At_a+v_b-At_b<0\ \ \ \ \ \ (v_a+v_b\geq 0)\\v_a+At_a+v_b+At_b>0\ \ \ \ \ \ (v_a+v_b<0)\end{cases}$$

也就是说,我们只需要对于正的 $v_a$ 和负的 $v_a$ 分开考虑,在保证取到右侧条件的基础上,维护一下最大最小值之类的东西就好了。

具体还是看代码吧。

代码

#include <bits/stdc++.h>
#define mp make_pair
#define fi first
#define se second
using namespace std;
typedef long long LL;
LL read(){
	LL x=0;
	char ch=getchar();
	while (!isdigit(ch))
		ch=getchar();
	while (isdigit(ch))
		x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
	return x;
}
const int N=50005;
const LL INF=1LL<<60;
int n;
LL K,ans=INF;
vector <pair <int,LL> > e[N];
int vis[N],size[N],Maxsize[N],root,Size;
void get_root(int x,int pre){
	size[x]=1,Maxsize[x]=0;
	for (auto E : e[x])
		if (E.fi!=pre&&!vis[E.fi]){
			get_root(E.fi,x);
			size[x]+=size[E.fi];
			Maxsize[x]=max(Maxsize[x],size[E.fi]);
		}
	Maxsize[x]=max(Maxsize[x],Size-size[x]);
	if (Maxsize[x]<Maxsize[root])
		root=x;
}
struct Node{
	int t,id;
	LL v;
	Node(int _t=0,LL _v=0,int _id=0){
		t=_t,v=_v,id=_id;
	}
	friend bool operator < (Node a,Node b){
		return a.v<b.v;
	}
}posi[N],nega[N];
int pc,nc;
void dfs(int x,int pre,int cnt,LL S,int ID){
	if (S>=0)
		posi[++pc]=Node{cnt,S,ID};
	else
		nega[++nc]=Node{cnt,S,ID};
	for (auto E : e[x])
		if (E.fi!=pre&&!vis[E.fi])
			dfs(E.fi,x,cnt+1,S+E.se,ID);
}
pair <int,LL> _1,_2;
void ckMax(pair <int,LL> _3){
	if (_3.se>_1.se){
		if (_3.fi!=_1.fi)
			_2=_1;
		_1=_3;
	}
	else if (_3.se>_2.se&&_3.fi!=_1.fi)
		_2=_3;
}
void ckMin(pair <int,LL> _3){
	if (_3.se<_1.se){
		if (_3.fi!=_1.fi)
			_2=_1;
		_1=_3;
	}
	else if (_3.se<_2.se&&_3.fi!=_1.fi)
		_2=_3;
}
int check(LL x){
	_1=_2=mp(0,INF);
	for (int i=1,j=nc;i<=pc;i++){
		while (j>0&&posi[i].v+nega[j].v>=0)
			ckMin(mp(nega[j].id,nega[j].v-x*nega[j].t)),j--;
		if (posi[i].v-x*posi[i].t+(posi[i].id==_1.fi?_2.se:_1.se)<0)
			return 1;
		ckMin(mp(posi[i].id,posi[i].v-x*posi[i].t));
	}
	_1=_2=mp(0,-INF);
	for (int i=pc,j=1;i>=1;i--){
		while (j<=nc&&posi[i].v+nega[j].v<0)
			ckMax(mp(nega[j].id,nega[j].v+x*nega[j].t)),j++;
		if (posi[i].v+x*posi[i].t+(posi[i].id==_1.fi?_2.se:_1.se)>0)
			return 1;
		ckMin(mp(posi[i].id,posi[i].v+x*posi[i].t));
	}
	return 0;
}
void solve(int x){
	Maxsize[0]=n+1;
	root=pc=nc=0;
	get_root(x,0);
	vis[x=root]=1;
	posi[++pc]=Node{0,0,x};
	for (auto E : e[x])
		if (!vis[E.fi])
			dfs(E.fi,x,1,E.se,E.fi);
	sort(posi+1,posi+pc+1);
	sort(nega+1,nega+nc+1);
	LL L=1,R=ans-1,mid;
	while (L<=R){
		mid=(L+R)>>1;
		if (check(mid))
			R=mid-1;
		else
			L=mid+1;
	}
	ans=min(ans,L);
	for (auto E : e[x])
		if (!vis[E.fi])
			Size=size[E.fi],solve(E.fi);
}
int main(){
	Size=n=read(),K=read();
	for (int i=1;i<n;i++){
		int a=read(),b=read();
		LL c=read()-K;
		ans=min(ans,abs(c)+1);
		e[a].push_back(mp(b,c));
		e[b].push_back(mp(a,c));
	}
	solve(1);
	printf("%lld\n",ans-1);
	return 0;
}

  

posted @ 2019-02-20 16:38  zzd233  阅读(229)  评论(0编辑  收藏  举报