【BZOJ4738/UOJ#276】汽水(点分治,分数规划)
【BZOJ4738/UOJ#276】汽水(点分治,分数规划)
题面
题解
今天考试的题目,虽然说是写完了,但是感觉还是半懂不懂的来着。
代码基本照着\(Anson\)爷的码的,orz。(然后Anson爷的UOJrk1不保了)
首先拿到这道题目的一个比较显然的思路就是分数规划二分答案之后再点分治考虑是否有满足二分条件的链。
考虑条件是什么呢?(接下来写的时候为了方便,把所有的边权默认全部减去了一个\(K\),这样子就是要求平均值的绝对值最小的链了)
因为要的是绝对值最小,那么我们二分了这个绝对值\(mid\)之后,只有两种情况,要么平均值小于\(0\),并且大于\(-mid\),或者大于\(0\)并且小于\(mid\)。移项之后变成了权值和减去边的数量乘以二分值的结果与\(0\)的大小关系。这两种情况分开考虑计算。
那么,我们要做的就是确定分治重心之后,求出过重心的所有链。先考虑其子树中的每一个点,记录三元组,分别表示权值和,边的数量,以及从哪个子树来的(显然只有两个不同子树中的链才能拼在一起),按照权值和排序之后考虑如何拼接。以权值和大于\(0\)为例。
对于每个权值和大于\(0\)的链从小往大加入贡献,找到权值最小的链满足与当前链的权值和大于\(0\),因为这个最小值是一段区间,所以维护下来两个最大值与当前权值为正的链拼接\(check\)是否满足条件即可。
好难说清楚啊,看下代码就懂了。
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<vector>
using namespace std;
#define ll long long
#define MAX 55000
inline ll read()
{
ll x=0;bool t=false;char ch=getchar();
while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
if(ch=='-')t=true,ch=getchar();
while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
return t?-x:x;
}
struct Line{int v,next;ll w;}e[MAX<<1];
int h[MAX],cnt=1;
inline void Add(int u,int v,ll w){e[cnt]=(Line){v,h[u],w};h[u]=cnt++;}
int sz[MAX],rt,Sz,mx;bool vis[MAX];
struct Node{ll v,s,t;}S[MAX];int top;
bool operator<(Node a,Node b){return a.s<b.s;}
ll ans=1ll<<60,K;int n;
void getroot(int x,int ff)
{
sz[x]=1;int ret=0;
for(int i=h[x];i;i=e[i].next)
{
int v=e[i].v;if(v==ff||vis[v])continue;
getroot(v,x);sz[x]+=sz[v];
ret=max(ret,sz[v]);
}
ret=max(ret,Sz-sz[x]);
if(ret<mx)mx=ret,rt=x;
}
void dfs(int u,int fa,int dep,ll sum,int tp)
{
S[++top]=(Node){dep,sum,tp};
for(int i=h[u];i;i=e[i].next)
if(e[i].v!=fa&&!vis[e[i].v])
dfs(e[i].v,u,dep+1,sum+e[i].w,tp);
}
int pos;
pair<ll,ll> A,B;
void upd(pair<ll,ll> c)
{
if(c.first<B.first)
{
if(c.first<A.first)
{
if(c.second!=A.second)B=A;
A=c;
}
else if(c.second!=A.second)B=c;
}
}
bool check1(ll k)
{
A=B=make_pair(1ll<<60,0);
for(int i=pos,j=pos-1;i<=top;++i)
{
while(j&&S[i].s+S[j].s>=0)
upd(make_pair(S[j].s-k*S[j].v,S[j].t)),--j;
if((A.second==S[i].t?B.first:A.first)<k*S[i].v-S[i].s)return true;
upd(make_pair(S[i].s-k*S[i].v,S[i].t));
}
return false;
}
bool check2(ll k)
{
A=B=make_pair(1ll<<60,0);
for(int i=pos-1,j=pos;i;--i)
{
while(j<=top&&S[i].s+S[j].s<0)upd(make_pair(-S[j].s+k*S[j].v,S[j].t)),++j;
if((A.second==S[i].t?B.first:A.first)<-k*S[i].v+S[i].s)return true;
upd(make_pair(-S[i].s+k*S[i].v,S[i].t));
}
return false;
}
void Divide(int x)
{
vis[x]=true;S[top=1]=(Node){0,0,0};
for(int i=h[x];i;i=e[i].next)
if(!vis[e[i].v])
dfs(e[i].v,x,1,e[i].w,e[i].v);
sort(&S[1],&S[top+1]);
for(pos=1;pos<=top&&S[pos].s<0;++pos);
ll l=1,r=ans-1;
while(l<=r)
{
ll mid=(l+r)>>1;
if(check1(mid)||check2(-mid))r=mid-1;
else l=mid+1;
}
ans=min(ans,l);
for(int i=h[x];i;i=e[i].next)
if(!vis[e[i].v])
Sz=mx=sz[e[i].v],getroot(e[i].v,x),Divide(rt);
}
int main()
{
n=read();K=read();
for(int i=1;i<n;++i)
{
int u=read(),v=read();ll w=read()-K;
Add(u,v,w);Add(v,u,w);ans=min(ans,abs(w)+1);
}
Sz=mx=n;getroot(1,0);
Divide(1);
printf("%lld\n",ans-1);
return 0;
}