[BZOJ2333][SCOI2011]棘手的操作(可并堆||线段树+离线)

211 篇文章 0 订阅
70 篇文章 0 订阅

题目描述

传送门

题解

对没错总会有一道题分到这个编号…233333

第一眼想到线段树,处理生成树(重新编号)使在任何时间在同一个连通块里的点都在一个连续的区间,然后搞搞搞就可以了
然而要是换成可并堆的话操作还真是棘手啊…

首先我们要维护两种可并堆(一个可并堆和一个splay也可以…),以下简称a堆和b堆
a堆是按照题目中的操作合并的,也就是说有若干个小堆
b堆只有一个堆,维护的是所有a堆的堆顶(最大值),也就是说堆中的节点数等于a堆的个数
a中每一个堆顶要维护一个标记add,表示当前这个堆中键值+add为实际值
维护全局ADD,表示总体加的值

U x y :在a中找到x和y中的根fx和fy,在b中删除fx和fy;在a中合并x和y,得到新的根top;将top插入b
A1 x v :在a中找到x的根fx,在a中删除x,在b中删除fx;修改x的键值,将x插入a,得到新根top;将top插入b
A2 x v :在a中找到x的根fx,在b中删除fx;在a中修改fx的标记,在b中修改fx的键值;在b中插入fx
A3 x :ADD+=v
F1 x :在a中找到x的根fx,key(x)+add(fx)+ADD
F2 x :在a中找到x的根fx,key(fx)+add(fx)+ADD
F3 x :找到b的根fx,key(fx)+ADD

注意:
a中每个根有标记add,堆中的键值+堆顶add才是正确值;b中没有标记,维护的键值即为实际值
U中将a堆合并时由于堆顶标记不同,需要修改某一个堆中的键值,使两堆标记相同。用启发式合并,每次暴力修改较小的堆,每个点只会被合并至多 logn

时间复杂度 O(nlogn) *一坨常数

写完可并堆了之后发现线段树的写法之前从来没写过又去写了一发…
值得一做的题.

代码

可并堆

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<queue>
using namespace std;
#define N 300005

int n,m,x,y,fx,fy,top,tmp,temp,v,ADD,lastsize,lastadd;
int ls[N],rs[N],dis[N],key[N],f[N],add[N],par[N],size[N];
char opt[5];
queue <int> q;

void Add(int x,int v)//启发式 
{
    if (ls[x]) Add(ls[x],v);
    if (rs[x]) Add(rs[x],v);
    key[x]+=v;
    return;
}
int find(int x)//找根 
{
    if (x==f[x]) return x;
    f[x]=find(f[x]);
    return f[x];
}
int merge(int x,int y)//合并 
{
    if (!x) return y;
    if (!y) return x;
    if (key[x]<key[y]) swap(x,y);
    rs[x]=merge(rs[x],y);
    par[rs[x]]=x;
    if (dis[ls[x]]<dis[rs[x]]) swap(ls[x],rs[x]);
    if (!rs[x]) dis[x]=0;
    else dis[x]=dis[rs[x]]+1;
    return x;
}
int del(int x)//删除x,返回x原先所在堆中的根(若删除x之后堆为空,则返回0) 
{
    int p,q,re;
    q=par[x];
    p=merge(ls[x],rs[x]);
    if (p) par[p]=q;
    if (q&&ls[q]==x) ls[q]=p;
    if (q&&rs[q]==x) rs[q]=p;
    if (!q) re=p;
    else re=find(q);
    while (q)
    {
        if (dis[ls[q]]<dis[rs[q]])
            swap(ls[q],rs[q]);
        if (dis[rs[q]]+1==dis[q])
            break;
        dis[q]=dis[rs[q]]+1;
        q=par[q];
    }
    ls[x]=rs[x]=dis[x]=par[x]=0;
    return re;
}

namespace st
{
    int ls[N],rs[N],dis[N],key[N],par[N];
    int root;
    int merge(int x,int y)//合并 
    {
        if (!x) return y;
        if (!y) return x;
        if (key[x]<key[y]) swap(x,y);
        rs[x]=merge(rs[x],y);
        par[rs[x]]=x;
        if (dis[ls[x]]<dis[rs[x]]) swap(ls[x],rs[x]);
        if (!rs[x]) dis[x]=0;
        else dis[x]=dis[rs[x]]+1;
        return x;
    }
    void clear()//左偏树构建 
    {
        for (int i=1;i<=n;++i) q.push(i);
        while (!q.empty())
        {
            x=q.front();q.pop();
            if (!q.empty()) {y=q.front();q.pop();}
            else break;
            top=merge(x,y);
            q.push(top);
        }
        st::root=x;
    }
    int del(int x)//删除x,返回根(可能为0) 
    {
        int p,q;
        q=par[x];
        p=merge(ls[x],rs[x]);
        if (p) par[p]=q;
        if (q&&ls[q]==x) ls[q]=p;
        if (q&&rs[q]==x) rs[q]=p;
        if (!q) root=p;
        while (q)
        {
            if (dis[ls[q]]<dis[rs[q]])
                swap(ls[q],rs[q]);
            if (dis[q]==dis[rs[q]]+1)
                break;
            dis[q]=dis[rs[q]]+1;
            p=q;
            q=par[q];
        }
        ls[x]=rs[x]=dis[x]=par[x]=0;
        f[x]=x;
        return root;
    }
}

int main()
{
    scanf("%d",&n);
    for (int i=1;i<=n;++i)
    {
        scanf("%d",&key[i]);
        st::key[i]=key[i];
    }
    for (int i=1;i<=n;++i) f[i]=i,size[i]=1;
    dis[0]=st::dis[0]=-1;
    st::clear();
    scanf("%d",&m);
    for (int i=1;i<=m;++i)
    {
        scanf("%s",opt);
        if (opt[0]=='U')
        {
            scanf("%d%d",&x,&y);
            fx=find(x),fy=find(y);
            if (fx!=fy)
            {
                tmp=st::del(fx);
                tmp=st::del(fy);

                if (size[fx]>size[fy])
                    swap(x,y),swap(fx,fy);
                Add(fx,add[fx]-add[fy]);
                top=merge(fx,fy);
                if (fx) f[fx]=top;
                if (fy) f[fy]=top;
                size[top]=size[fx]+size[fy];
                add[top]=add[fy];

                temp=st::merge(tmp,top);
                st::root=temp;
            }
        }
        else if (opt[0]=='A')
        {
            if (opt[1]=='1')
            {
                scanf("%d%d",&x,&v);
                fx=find(x);

                tmp=st::del(fx);

                lastsize=size[fx];
                lastadd=add[fx];
                y=del(x);
                key[x]+=v;
                top=merge(x,y);
                if (x) f[x]=top;
                if (y) f[y]=top;
                size[top]=lastsize;
                add[top]=lastadd;

                st::key[top]=key[top]+add[top];
                temp=st::merge(tmp,top);
                st::root=temp;
            }
            else if (opt[1]=='2')
            {
                scanf("%d%d",&x,&v);
                fx=find(x);

                tmp=st::del(fx);

                add[fx]+=v;

                st::key[fx]=key[fx]+add[fx];
                temp=st::merge(tmp,fx);
                st::root=temp;
            }
            else if (opt[1]=='3')
            {
                scanf("%d",&v);
                ADD+=v;
            }
        }
        else if (opt[0]=='F')
        {
            if (opt[1]=='1')
            {
                scanf("%d",&x);
                fx=find(x);
                printf("%d\n",key[x]+add[fx]+ADD);
            }
            else if (opt[1]=='2')
            {
                scanf("%d",&x);
                fx=find(x);
                printf("%d\n",key[fx]+add[fx]+ADD);
            }
            else if (opt[1]=='3')
            {
                x=st::root;
                printf("%d\n",st::key[x]+ADD);
            }
        }
    }
    return 0;
}

线段树+离线

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<queue>
using namespace std;
#define N 300005
#define inf 2100000000

int n,m,fx,fy,cnt,ans;
char opt[N][3];
int a[N],x[N],y[N],v[N],f[N],size[N],from[N],to[N],l[N],r[N],pt[N],val[N];
int maxn[N*4],delta[N*4];

int find(int x)
{
    if (x==f[x]) return x;
    f[x]=find(f[x]);
    return f[x];
}
void update(int now)
{
    maxn[now]=max(maxn[now<<1],maxn[now<<1|1]);
}
void pushdown(int now,int l,int r,int mid)
{
    if (delta[now])
    {
        maxn[now<<1]+=delta[now];delta[now<<1]+=delta[now];
        maxn[now<<1|1]+=delta[now];delta[now<<1|1]+=delta[now];
        delta[now]=0;
    }
}
void build(int now,int l,int r)
{
    int mid=(l+r)>>1;
    if (l==r)
    {
        maxn[now]=val[l];
        return;
    }
    build(now<<1,l,mid);
    build(now<<1|1,mid+1,r);
    update(now);
}
void change(int now,int l,int r,int lrange,int rrange,int v)
{
    int mid=(l+r)>>1;
    if (lrange<=l&&r<=rrange)
    {
        maxn[now]+=v;
        delta[now]+=v;
        return;
    }
    pushdown(now,l,r,mid);
    if (lrange<=mid) change(now<<1,l,mid,lrange,rrange,v);
    if (mid+1<=rrange) change(now<<1|1,mid+1,r,lrange,rrange,v);
    update(now);
}
int query(int now,int l,int r,int lrange,int rrange)
{
    int mid=(l+r)>>1,ans=-inf;
    if (lrange<=l&&r<=rrange) return maxn[now];
    pushdown(now,l,r,mid);
    if (lrange<=mid) ans=max(ans,query(now<<1,l,mid,lrange,rrange));
    if (mid+1<=rrange) ans=max(ans,query(now<<1|1,mid+1,r,lrange,rrange));
    return ans;
}
int main()
{
    scanf("%d",&n);
    for (int i=1;i<=n;++i) scanf("%d",&a[i]);
    for (int i=1;i<=n;++i) f[i]=i,size[i]=1;
    scanf("%d",&m);
    for (int i=1;i<=m;++i)
    {
        scanf("%s",opt[i]);
        if (opt[i][0]=='U')
        {
            scanf("%d%d",&x[i],&y[i]);
            fx=find(x[i]),fy=find(y[i]);
            if (fx==fy) continue;
            if (fx<fy) swap(x[i],y[i]),swap(fx,fy);
            size[fy]+=size[fx];
            f[fx]=fy;
            from[++cnt]=fx;to[cnt]=fy;
        }
        else if (opt[i][0]=='A')
        {
            if (opt[i][1]=='1')
                scanf("%d%d",&x[i],&v[i]);
            else if (opt[i][1]=='2')
                scanf("%d%d",&x[i],&v[i]);
            else if (opt[i][1]=='3')
                scanf("%d",&v[i]);
        }
        else if (opt[i][0]=='F')
        {
            if (opt[i][1]=='1')
                scanf("%d",&x[i]);
            else if (opt[i][1]=='2')
                scanf("%d",&x[i]);
        }
    }
    int last=0;
    for (int i=1;i<=n;++i)
        if (find(i)==i&&size[i]>1)
        {
            l[i]=last+1;
            r[i]=pt[i]=last+size[i];
            last+=size[i];
        }
    for (int i=cnt;i>=1;--i)
    {
        r[from[i]]=pt[from[i]]=pt[to[i]];
        l[from[i]]=r[from[i]]-size[from[i]]+1;
        pt[to[i]]=l[from[i]]-1;
    }
    for (int i=1;i<=n;++i)
        if (find(i)==i&&size[i]==1)
            l[i]=r[i]=++last;
    for (int i=1;i<=n;++i) f[i]=i,size[i]=1,val[l[i]]=a[i];
    build(1,1,n);
    for (int i=1;i<=m;++i)
    {
        if (opt[i][0]=='U')
        {
            fx=find(x[i]),fy=find(y[i]);
            if (fx==fy) continue;
            if (fx<fy) swap(fx,fy),swap(x[i],y[i]);
            f[fx]=fy;
            size[fy]+=size[fx];
        }
        else if (opt[i][0]=='A')
        {
            if (opt[i][1]=='1')
                change(1,1,n,l[x[i]],l[x[i]],v[i]);
            else if (opt[i][1]=='2')
            {
                fx=find(x[i]);
                change(1,1,n,l[fx],l[fx]+size[fx]-1,v[i]);
            }
            else if (opt[i][1]=='3')
                change(1,1,n,1,n,v[i]);
        }
        else if (opt[i][0]=='F')
        {
            if (opt[i][1]=='1')
            {
                ans=query(1,1,n,l[x[i]],l[x[i]]);
                printf("%d\n",ans);
            }
            else if (opt[i][1]=='2')
            {
                fx=find(x[i]);
                ans=query(1,1,n,l[fx],l[fx]+size[fx]-1);
                printf("%d\n",ans);
            }
            else if (opt[i][1]=='3')
            {
                ans=query(1,1,n,1,n);
                printf("%d\n",ans);
            }
        }
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值