Codeforces 452 E. Segments Removal

题目链接

简介:
给出一个正整数序列,每次选择删除数字相同的一个连续区间(如果有多个就选择最靠左的),询问需要几次删除操作才能使序列为空

分析:
ZYXZYXZYX和澍神做的题,说是练数据结构,于是就做做看啦

看到题目,第一个反应:unique?set?
但是我的STL好似是废掉的。。。

那就用平衡树搞
我们可以采用缩点的方法,把一个区间缩成一个点,扔进splay中

在每个维点中多维护一个信息mx:该子树中的最长区间长度

那么我们在删除的时候,只要看一下mx[root],就知道当前需要删除的区间长度了

那我们要怎么确定到底删除那个区间呢?
因为我们要找到最靠左的合法区间,这项任务我交给了find函数:

int find(int num)
{
    int now=root;
    while (1)
    {
        if (ch[now][0]&&mx[ch[now][0]]==num) now=ch[now][0];    //寻找尽量靠前的区间 
        else if (sz[now]==num) return now;
        else if (ch[now][1]&&mx[ch[now][1]]==num) now=ch[now][1];
    }
}

但是一旦有删除操作,就有可能触发序列的一个隐藏技能:合并
实际上这个也比较容易处理:
我们只要比较此次删除的结点x的前驱pr和后继ls
看看这两个区间的数值是否一样:

  • val[pr]=val[ls]
    不用理ta
  • val[pr]=val[ls]
    我们选择删除一个点,把两个点的信息合并到另一个点上

tip

注意del的最后几句:

int k=root;
int pr=fro();
splay(pr,0); 
ch[root][1]=ch[k][1];
pre[ch[k][1]]=root;
clear(k);
update(root);

我们要规范化代码

//这里写代码片
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>

using namespace std;

const int N=200010;
int ch[N][2],sz[N],pre[N],mx[N],v[N];
int top=0,root=0,a[N][2],n;

int get(int bh)
{
    return ch[pre[bh]][0]==bh? 0:1;
}

void clear(int bh)
{
    ch[bh][0]=ch[bh][1]=sz[bh]=pre[bh]=mx[bh]=v[bh]=0;
}

void update(int bh)
{
    if (!bh) return;
    int lc=ch[bh][0];
    int rc=ch[bh][1];
    mx[bh]=sz[bh];
    if (lc) mx[bh]=max(mx[bh],mx[lc]);  //最长连续长度 
    if (rc) mx[bh]=max(mx[bh],mx[rc]);
}

void rotate(int bh)
{
    int fa=pre[bh];
    int grand=pre[fa];
    int wh=get(bh);
    ch[fa][wh]=ch[bh][wh^1];
    pre[ch[fa][wh]]=fa;
    ch[bh][wh^1]=fa;
    pre[fa]=bh;
    pre[bh]=grand;
    if (grand) ch[grand][ch[grand][0]==fa ? 0:1]=bh;
    update(fa);
    update(bh);
}

void splay(int bh,int mb)
{
    for (int fa;(fa=pre[bh])!=mb;rotate(bh))
        if (pre[fa]!=mb)
            rotate(get(bh)==get(fa)? fa:bh);
    if (!mb) root=bh;
}

int build(int l,int r,int fa)
{
    if (r<l) return 0;
    int now=++top;
    int mid=(l+r)>>1;
    ch[now][0]=build(l,mid-1,now);
    ch[now][1]=build(mid+1,r,now);
    v[now]=a[mid][0]; sz[now]=a[mid][1];
    pre[now]=fa;
    update(now);
    return now;
}

int fro()
{
    int now=ch[root][0];
    while (ch[now][1]) now=ch[now][1];
    return now;
}

int nxt()
{
    int now=ch[root][1];
    while (ch[now][0]) now=ch[now][0];
    return now;
}

void del()   //删除根结点 
{
    if (!ch[root][0]&&!ch[root][1])
    {
        clear(root);
        root=0;
        return;
    }
    else if (!ch[root][0])
    {
        int k=root;
        root=ch[k][1];
        pre[root]=0;
        clear(k);
        return;
    }
    else if (!ch[root][1])
    {
        int k=root;
        root=ch[k][0];
        pre[root]=0;
        clear(k);
        return;
    }
    int k=root;
    int pr=fro();
    splay(pr,0); 
    ch[root][1]=ch[k][1];
    pre[ch[k][1]]=root;
    clear(k);
    update(root);
}

void merge()
{
    int pr=fro(),ls=nxt(); 
    del();
    if (v[pr]!=v[ls]) return;
    if (!pr||!ls) return;        //有一个结点不存在 
    int num=sz[ls];
    splay(ls,0); del();
    splay(pr,0); sz[root]+=num;  //合并信息 
    update(root);
}

int find(int num)
{
    int now=root;
    while (1)
    {
        if (ch[now][0]&&mx[ch[now][0]]==num) now=ch[now][0];    //寻找尽量靠前的区间 
        else if (sz[now]==num) return now;
        else if (ch[now][1]&&mx[ch[now][1]]==num) now=ch[now][1];
    }
}

int main()
{
    scanf("%d",&n);
    int x,pr=-1,cnt=0;
    for (int i=1;i<=n;i++)
    {
        scanf("%d",&x);
        if (x==pr) a[cnt][1]++;
        else{
            cnt++;
            pr=x; 
            a[cnt][0]=x; a[cnt][1]=1;
        }
    }

    root=build(1,cnt,0);
    int tot=0;
    while (root)
    {
        tot++;
        int num=mx[root];   //区间长度 
        splay(find(num),0);
        merge();
    }
    printf("%d",tot);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值