首页 > 技术文章 > Luogu6670 [清华集训2016] 汽水

GK0328 2020-10-29 09:27 原文

Luogu6670 [清华集训2016] 汽水

网上说这道题是点分治

反正蒟蒻用边分治切了这道题。

题意就是求一条链,使它的平均权值与\(k\)的绝对值最小。

设任意一条链为\(\{w_1,w_2,\cdots ,w_t\}\)

求:

\[\min \lvert \frac{\sum_{i=1}^t w_i}{t}-k \rvert \]

我们考虑二分答案,然后我们需要判断是否存在符合条件的链。

我们二分的是\(\lvert \frac{\sum_{i=1}^t w_i}{t}-k \rvert\),它是非负的。

如果我们二分到\(mid\)是合法的,应当存在链满足:

\[\lvert \frac{\sum_{i=1}^t w_i}{t}-k \rvert \le mid \]

\(k\)加入分母中。

\[\frac{\sum_{i=1}^t w_i}{t}-k=\\ \frac{(\sum_{i=1}^t w_i)-tk}{t}=\\ \frac{\sum_{i=1}^t (w_i-k)}{t} \]

拆开绝对值。

\[-mid \le \frac{\sum_{i=1}^t (w_i-k)}{t} \le mid \]

\[\begin{cases} \frac{\sum_{i=1}^t (w_i-k-mid)}{t} \le 0\\ \frac{\sum_{i=1}^t (w_i-k+mid)}{t} \ge 0 \end{cases} \]

我们可以对一条边定两个边权\(w_i-k-mid,w_i-k+mid\),那么对于一条链,我们会获得两个链长\(L1,L2\)

我们需要满足\(L1 \le 0,L2 \ge 0\)

考虑边分治,对于两端的链,自己作为一条链统计贡献,与另一个连通块中的链合并仍需要统计贡献。

现在就需要解决快速链合并。

我们现在可以转化为序列问题了。

给你两个由数对\((x,y)\)组成的序列\(A,B\),你需要判断是否存在一个\(A\)中的数对\(p\)\(B\)中的数对\(q\),满足\(p_x+q_x \le 0,p_y+q_y \ge 0\)

我们可以先把两个序列按\(x\)从小到大排序。

那么对于一个\(A\)中的数对\(p\),与\(p\)满足\(p_x+q_x \le 0\)\(B\)中数对一定在一段连续的区间中,我们只需要比较\(p_y\)和这段区间中\(y\)的最大值匹配即可。

观察发现,如果我们对\(A\)从大到小扫一遍,那么合法的区间一定从\([1,z]\)开始(\(z\)可以小于\(1\),表示不存在合法方案),\(z\)不断扩大,而左端点\(1\)不改变,直接一个指针扫过去就好了。

注意点:

\(1.\)由于单个点不能算作一条链,我们必须判断一个节点是否走过一条边,否则不能加入匹配序列中,而在边分树上走过一些虚点后到达它们的父亲节点仍然没有走过一条边,需要特判。

\(2.\)二分答案时,由于只需要保留整数,我们按整数来二分。这道题需要向下取整,我们二分到的答案可能需要\(-1\),比如\(1.5\)二分到的答案为\(2\),那么我们对二分得到的答案进行一次边分治,只要判断是否有不取等号的解即可(操作就是把所有的\(\le,\ge\)改成\(<,>\))。

\(Code:\)

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<vector>
#include<cstdlib>
#define N 50005
#define M 100005
#define ll long long
#define pr pair<int,ll>
#define lpr pair<ll,ll>
#define mp make_pair
using namespace std;
const ll INF=1919191919191919;
const int iINF=1000000009;
vector< pr >E[N];
#define IT vector< pr > :: iterator
int n,x,y,rt,rtsz;
ll k,z,mx,mn,mid,ans;
int tot=1,cnt;
bool flag,flag2;
struct node
{
    int nxt,v;
    ll cost;
    node (int Nxt=0,int V=0,ll Cost=0)
    {
        nxt=Nxt,v=V,cost=Cost;
    }
}e[M << 1];
int fr[M],sz[M],cp[M];
bool vis[M << 1];
ll cdp1[M],cdp2[M];
int qcp,cc[2];
lpr c[2][N];
void add(int x,int y,ll z)
{
    ++tot;
    e[tot]=node(fr[x],y,z),fr[x]=tot;
}
void add_edge(int x,int y,ll z)
{
    add(x,y,z),add(y,x,z);
}
void build(int u,int F)
{
    int les=E[u].size()-(int)(u!=1);
    int tt=0,lst;
    for (IT it=E[u].begin();it!=E[u].end();++it)
    {
        int v=it->first;
        ll cost=it->second;
        if (v==F)
            continue;
        ++tt;
        if (tt==1)
            add_edge(u,v,cost),lst=u; else
        if (tt==les)
            add_edge(lst,v,cost); else
            {
                add_edge(lst,++cnt,0);
                add_edge(cnt,v,cost);
                lst=cnt;
                cp[cnt]=u;
            }
        build(v,u);
    }
}
void getrt(int u,int F,int rn)
{
    sz[u]=1;
    for (int i=fr[u];i;i=e[i].nxt)
    {
        int v=e[i].v;
        if (v==F || vis[i])
            continue;
        getrt(v,u,rn);
        sz[u]+=sz[v];
        if (max(sz[v],rn-sz[v])<rtsz)
            rtsz=max(sz[v],rn-sz[v]),rt=i;
    }
}
void dfs2(int u,int F,int cl)
{
    if (u<=n && u!=qcp)
        c[cl][++cc[cl]]=mp(cdp1[u],cdp2[u]);
    for (int i=fr[u];i;i=e[i].nxt)
    {
        int v=e[i].v;
        ll cost=e[i].cost-k;
        if (v==F || vis[i])
            continue;
        if (cp[u]!=v && cp[v]!=u && !(u>n && v>n))
            cdp1[v]=cdp1[u]+cost-mid,cdp2[v]=cdp2[u]+cost+mid; else
            cdp1[v]=cdp1[u],cdp2[v]=cdp2[u];
        dfs2(v,u,cl);
    }
}
void solve(int u,int s)
{
    rtsz=iINF;
    getrt(u,0,s);
    if (rtsz==iINF)
        return;
    vis[rt]=vis[rt^1]=true;
    int v1=e[rt].v,v2=e[rt^1].v;
    cc[0]=cc[1]=0;
    if (v1<=n)
        qcp=v1; else
        qcp=cp[v1];
    cdp1[v1]=cdp2[v1]=0;
    dfs2(v1,0,0);
    if (cp[v1]!=v2 && cp[v2]!=v1 && !(v1>n && v2>n))
        cdp1[v2]=e[rt].cost-k-mid,cdp2[v2]=e[rt].cost-k+mid; else
        {   
            if (v2<=n)
                qcp=v2; else
                qcp=cp[v2];
            cdp1[v2]=cdp2[v2]=0;
        }
    dfs2(v2,0,1);
    for (int i=1;i<=cc[0];++i)
        if ((!flag2 && c[0][i].first<=0 || flag2 && c[0][i].first<0) && (!flag2 && c[0][i].second>=0 || flag2 && c[0][i].second>0))
        {
            flag=true;
            vis[rt]=vis[rt^1]=false;
            return;
        }
    for (int i=1;i<=cc[1];++i)
        if ((!flag2 && c[1][i].first<=0 || flag2 && c[1][i].first<0) && (!flag2 && c[1][i].second>=0 || flag2 && c[1][i].second>0))
        {
            flag=true;
            vis[rt]=vis[rt^1]=false;
            return;
        }
    sort(c[0]+1,c[0]+cc[0]+1),sort(c[1]+1,c[1]+cc[1]+1);
    ll mx=-INF;
    int l=1;
    for (int i=cc[0];i;--i)
    {
        while (l<=cc[1] && (!flag2 && c[1][l].first+c[0][i].first<=0 || flag2 && c[1][l].first+c[0][i].first<0))
        {
            mx=max(mx,c[1][l].second);
            ++l;
        }
        if (l!=1 && (!flag2 && mx+c[0][i].second>=0 || flag2 && mx+c[0][i].second>0))
        {
            flag=true;
            vis[rt]=vis[rt^1]=false;
            return;
        }
    }
    int kt=rt,ts=s-sz[v1];
    solve(v1,sz[v1]);
    if (flag)
    {
        vis[kt]=vis[kt^1]=false;
        return;
    }
    solve(v2,ts);
    vis[kt]=vis[kt^1]=false;
}
bool check()
{
    flag=false;
    solve(1,cnt);
    return flag;
}
int main()
{
    scanf("%d%lld",&n,&k),cnt=n;
    mx=0,mn=INF;
    for (int i=1;i<n;++i)
    {
        scanf("%d%d%lld",&x,&y,&z);
        E[x].push_back(mp(y,z)),E[y].push_back(mp(x,z));
        mx=max(mx,z),mn=min(mn,z);
    }
    build(1,0);
    ll l=0,r=min(abs(k-mx),abs(k-mn));
    while (l<=r)
    {
        mid=(l+r) >> 1;
        if (check())
            ans=mid,r=mid-1; else
            l=mid+1;
    }
    mid=ans;
    flag2=true;
    if (ans && check())
        --ans;
    printf("%lld\n",ans);
    return 0;
}

推荐阅读