题目链接
之前想用线段树,,写了半天没有写出来。
然后就看了浅蓝大佬的博客 转自------点击打开链接
还是有一点不太懂,,回去慢慢研究。 orz
官方题解。
考虑用树状数组维护每一个位置是否为一段颜色的起点(下简称“起点”)。 询问时,只需要查询区间内起点个数,再特判左端点是否为起点,即可求得答案。 针对合并操作,如果暴力合并,复杂度显然是O(n2)的,尝试用启发式合并优化它。 用数组维护每种颜色的位置个数,合并时,将个数少的颜色全部修改成个数多的颜色。 由于具体实现仍与暴力合并类似,因此可以轻易地维护前面提到的树状数组。 需要注意的是,由于合并时可能交换颜色,因此还需要维护每个数代表的真实颜色。 由于采用了启发式合并,因此时间复杂度为O(nlogn+Qlog2n)或O(nlogn+Qlogn)。 本题也可以用线段树或其他很多数据结构解决。
#include<bits/stdc++.h> #define LL long long using namespace std; const int maxn=1e6+10; int ft[maxn],nt[maxn],u[maxn],cnt,n; int f[maxn],g[maxn],a[maxn],q[maxn]; int lowbit(int x){ return x&(-x); } void add(int x,int y){ for(int i=x;i<=n;i+=lowbit(i)) q[i]+=y; } int sum(int x){ int s=0; for(int i=x;i;i-=lowbit(i)) s+=q[i]; return s; } int main(){ int T,m,x,y,z; scanf("%d",&T); while(T--){ scanf("%d%d",&n,&m); memset(ft,-1,sizeof(ft)); memset(f,0,sizeof(f)); memset(g,0,sizeof(g)); memset(q,0,sizeof(q)); cnt=0;a[0]=a[n+1]=-1; for(int i=1;i<=n;i++){ scanf("%d",&a[i]); f[a[i]]=a[i];g[a[i]]++;u[cnt]=i;nt[cnt]=ft[a[i]];ft[a[i]]=cnt++; if(a[i]!=a[i-1]) add(i,1); } for(int i=0;i<m;i++){ scanf("%d%d%d",&x,&y,&z); if(x==2) printf("%d\n",sum(z)-sum(y-1)+(int)(a[y]==a[y-1])); else{ if(y==z||!g[f[y]]) continue;//如果要改变的y和z相等,或是y不存在。直接GG int s=y,t=z; if(g[f[y]]>g[f[z]]) swap(y,z);//将颜色少的改成颜色多的。 y=f[y],z=f[z]; for(int j=ft[y];j!=-1;j=nt[j]){//然后一个一个判断增减。。。。 if (a[u[j]] != a[u[j] - 1]) add(u[j], -1); if (a[u[j]] != a[u[j] + 1]) add(u[j] + 1, -1); a[u[j]] = z; if (a[u[j]] != a[u[j] - 1]) add(u[j], 1); if (a[u[j]] != a[u[j] + 1]) add(u[j] + 1, 1); if (nt[j] == -1) { nt[j] = ft[z]; ft[z] = ft[y]; break; } } g[z] += g[y]; g[y] = 0; f[t] = z; f[s] = 0; } } } return 0; }