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