[BZOJ 4071] 巴邻旁之桥

Link:

BZOJ 4071传送门

Solution:

首先算出能提前算的贡献

$K=1$:肯定选中间的点,小学数学

$K=2$:对于每对$(x,y)$一定选离$(x+y)/2$近的桥

也就是说将$(x,y)$按$(x+y)/2$的值排序后一定恰有一个分割点使得两边选择不同的桥!

考虑如何如何快速枚举所有分割点时的答案:

需要支持插入、删除、求中位数及两边的和,明显选择$Splay$来维护(求和更容易些)

这样先将所有数对加进一个$Splay$,再不断将其中的数移向另一棵$Splay$即可

注意:可能以前的$Splay$板子容易出不少锅啊……

首先$Update$时最好对$z$进行判断,然后$Splay$最后也要$Pushup$否则会在树高低时不更新$size$

Code:

#include <bits/stdc++.h>

using namespace std;
#define X first
#define Y second
typedef double db;
typedef long long ll;
typedef pair<int,int> P;
const int MAXN=2e5+10;//注意范围 
const ll INF=1<<30;

struct Splay
{
    ll sum[MAXN];
    int rt,tot,f[MAXN],sz[MAXN],cnt[MAXN],ch[MAXN][2],val[MAXN];
    void update(int x)
    {
        sz[x]=sz[ch[x][0]]+sz[ch[x][1]]+cnt[x];
        sum[x]=sum[ch[x][0]]+sum[ch[x][1]]+1ll*val[x]*cnt[x];
    }
    void rotate(int x)
    {
        int y=f[x],z=f[y],k=(ch[y][1]==x);
        //最好对z特判一下防止对ch[0]操作 
        if(z) ch[z][ch[z][1]==y]=x;f[x]=z;
        ch[y][k]=ch[x][k^1];f[ch[x][k^1]]=y;
        ch[x][k^1]=y;f[y]=x;
        update(y);update(x);
    }
    void splay(int x,int up)
    {
        while(f[x]!=up)
        {
            int y=f[x],z=f[y];
            if(z!=up) (ch[y][0]==x)^(ch[z][0]==y)?rotate(x):rotate(y);
            rotate(x);
        }
        if(!up) rt=x;
        //!!!!!!!!
        update(x);//可能f[x]=up,但也要update 
    }
    void insert(int x)
    {
        int k=rt,anc=0;
        while(k&&val[k]!=x)
            anc=k,k=ch[k][x>val[k]];
        if(k) cnt[k]++;
        else
        {
            k=++tot;
            if(anc) ch[anc][x>val[anc]]=k;
            val[k]=x;cnt[k]=1;f[k]=anc;sz[k]=1;
        }
        splay(k,0);
    }
    void find(int x)
    {
        int k=rt;
        while(x!=val[k]&&ch[k][x>val[k]])
            k=ch[k][x>val[k]];
        splay(k,0);
    }
    int kth(int x)
    {
        int k=rt;
        //对当前为空的情况特判 
        if(sz[k]<x||!x) return 0;
        while(true)
        {
            if(x>sz[ch[k][0]]+cnt[k])
                x-=sz[ch[k][0]]+cnt[k],k=ch[k][1];
            else if(x<=sz[ch[k][0]]) k=ch[k][0];
            else return k;
        }
    }
    //不用加边界的删除法 
    void del(int x)
    {
        find(x);
        if(val[rt]!=x) return;
        if(cnt[rt]>1) cnt[rt]--;
        else if(!ch[rt][0]||!ch[rt][1])
        {
            int k=ch[rt][0]+ch[rt][1];
            f[k]=0;rt=k;
        }
        else
        {
            int k=ch[rt][0];
            while(ch[k][1]) k=ch[k][1];
            splay(k,rt);
            ch[k][1]=ch[rt][1];
            f[ch[k][1]]=k;
            rt=k;f[k]=0;
        }
        //由于后面没有splay了要update 
        update(rt);
    }
    ll query()
    {
        int k=kth(sz[rt]/2);
        if(!k) return 0;splay(k,0);
        ll ret1=1ll*val[k]*sz[ch[k][0]]-sum[ch[k][0]];
        ll ret2=sum[ch[k][1]]-1ll*val[k]*sz[ch[k][1]];
        return ret1+ret2;
    }
}s1,s2;

char a[10],b[10];
ll res=1ll<<60,pre;
int n,x,y,k,tot;P dat[MAXN];

bool cmp(P a,P b){return a.X+a.Y<b.X+b.Y;};

int main()
{
    scanf("%d%d",&k,&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%s%d%s%d",a,&x,b,&y);
        if(a[0]==b[0]) pre+=abs(x-y);
        else dat[++tot]=P(x,y),pre++;
    }
    //注意特判!!!
    if(!tot) return printf("%lld",pre),0;
    sort(dat+1,dat+tot+1,cmp);
    
    for(int i=1;i<=tot;i++)
        s1.insert(dat[i].X),s1.insert(dat[i].Y);
    if(k==1) return printf("%lld",s1.query()+pre),0;
    
    for(int i=1;i<=tot;i++)
    {
        s1.del(dat[i].X);s1.del(dat[i].Y);
        s2.insert(dat[i].X);s2.insert(dat[i].Y);
        res=min(res,s1.query()+s2.query());
    }
    printf("%lld",res+pre);
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/newera/p/9717687.html