Description
N个布丁摆成一行,进行M次操作.每次将某个颜色的布丁全部变成另一种颜色的,然后再询问当前一共有多少段颜色.
例如颜色分别为1,2,2,1的四个布丁一共有3段颜色.
Input
第一行给出N,M表示布丁的个数和好友的操作次数.
第二行N个数A1,A2...An表示第i个布丁的颜色从第三行起有M行,
对于每个操作,
若第一个数字是1表示要对颜色进行改变,其后的两个整数X,Y表示将所有颜色为X的变为Y,X可能等于Y.
若第一个数字为2表示要进行询问当前有多少段颜色,这时你应该输出一个整数. 0。
n,m<=1000000
Output
针对第二类操作即询问,依次输出当前有多少段颜色.
Sample Input
4 3
1 2 2 1
2
1 2 1
2
1 2 2 1
2
1 2 1
2
Sample Output
3
1
1
调了一下午终于A掉了qwq……
学会启发式合并后,其实这个题就是裸题了
splay启发式合并,听起来很智能,只不过就是暴力将小splay上的点一个个insert到大splay上
这个题只需要将需要改的颜色的splay合并到目标颜色splay上就好了
(这个程序并没有从小的合并到大的,不过仍然很快,可能是数据太弱了)
只不过程序中有一些小细节需要注意就是了
细节详见code
学会启发式合并后,其实这个题就是裸题了
splay启发式合并,听起来很智能,只不过就是暴力将小splay上的点一个个insert到大splay上
这个题只需要将需要改的颜色的splay合并到目标颜色splay上就好了
(这个程序并没有从小的合并到大的,不过仍然很快,可能是数据太弱了)
只不过程序中有一些小细节需要注意就是了
细节详见code
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#define N (1000000+100)
using namespace std;
int Size[N],Key[N];//key[i]中存的是编号为i的颜色,用序列的编号大小来建splay,节点里存颜色
int Father[N],Son[N][2];
int n,m,Root[N],ans,a[N];
int flag;
int Get(int x){return Son[Father[x]][1]==x;}
void Update(int x){Size[x]=Size[Son[x][0]]+Size[Son[x][1]]+1;}
void Clear(int x){Key[x]=Father[x]=Son[x][0]=Son[x][1]=Size[x]=0;}
void Rotate(int x)
{
int wh=Get(x);
int fa=Father[x],fafa=Father[fa];
Son[fa][wh]=Son[x][wh^1];
Father[fa]=x;
if (Son[fa][wh]) Father[Son[fa][wh]]=fa;
Son[x][wh^1]=fa;
Father[x]=fafa;
if (fafa) Son[fafa][Son[fafa][1]==fa]=x;
Update(fa);
Update(x);
}
void Splay(int x,int &root)
{
for (int fa;fa=Father[x];Rotate(x))
if (Father[fa])
Rotate(Get(fa)==Get(x)?fa:x);
root=x;
}
void Insert(int x,int color,int &root)
{
if (root==0)
{
Size[x]=1;
Key[x]=color;
root=x;
return;
}
int now=root,fa=0;
while (1)
{
fa=now;now=Son[now][x>now];
if (now==0)
{
Size[x]=1;
Key[x]=color;
Father[x]=fa;
Son[fa][x>fa]=x;
Update(fa);
Splay(x,root);
return;
}
}
}
void Change(int x,int &root,int color)//唯一需要注意的函数,参数:(被拆的splay的当前节点,合并到的splay的根,需要改成什么颜色)。按中序遍历将要拆的splay一个个拆掉就好
{
if (Son[x][0]) Change(Son[x][0],root,color);
int rson=Son[x][1];//因为下面要clear x这个节点的信息,所以事先存一下
int pre_color=Key[x];//同上
if (!flag && Key[x]!=Key[x-1]) flag=1+(color==Key[x-1]);//☆如果x是连续序列的开始,就把flag改成1。如果不仅是开始,还能和前一个连起来,flag再加一。(flag在连续序列结束的地方会用到)
Clear(x);
Insert(x,color,root);//插入到目标splay中
if (Key[x]==Key[x+1]) ans-=1;//如果当前是连续序列的结束且这个点能和后面的颜色接上,ans--。
if (Key[x+1]!=pre_color) ans-=flag-1,flag=0;//如果这个点是结束,ans根据开头是否接上判断减不减。flag归零。
if (rson) Change(rson,root,color);
}
int main()
{
scanf("%d%d",&n,&m);
for (int i=1;i<=n;++i)
{
scanf("%d",&a[i]);
if (a[i]!=a[i-1]) ans++;
Insert(i,a[i],Root[a[i]]);
}
int opt,x,y;
for (int i=1;i<=m;++i)
{
scanf("%d",&opt);
if (opt==1)
{
scanf("%d%d",&x,&y);
if (Root[x]==0 || x==y) continue;//如果当前颜色没有节点 或者 这次更改前后的颜色是一样的 就不用做了(因为做了会卡)
Change(Root[x],Root[y],y);
Root[x]=0;
}
if (opt==2) printf("%d\n",ans);
}
}