1. 程式人生 > >【BZOJ4738/UOJ#276】汽水(點分治,分數規劃)

【BZOJ4738/UOJ#276】汽水(點分治,分數規劃)

【BZOJ4738/UOJ#276】汽水(點分治,分數規劃)

題面

BZOJ
UOJ

題解

今天考試的題目,雖然說是寫完了,但是感覺還是半懂不懂的來著。
程式碼基本照著\(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;
}