hdu 5997 rausen loves cakes

题目链接

点击打开链接


之前想用线段树,,写了半天没有写出来。

然后就看了浅蓝大佬的博客  转自------点击打开链接

还是有一点不太懂,,回去慢慢研究。 orz

官方题解。

考虑用树状数组维护每一个位置是否为一段颜色的起点(下简称“起点”)。 询问时,只需要查询区间内起点个数,再特判左端点是否为起点,即可求得答案。 针对合并操作,如果暴力合并,复杂度显然是O(n^2)O(n2)的,尝试用启发式合并优化它。 用数组维护每种颜色的位置个数,合并时,将个数少的颜色全部修改成个数多的颜色。 由于具体实现仍与暴力合并类似,因此可以轻易地维护前面提到的树状数组。 需要注意的是,由于合并时可能交换颜色,因此还需要维护每个数代表的真实颜色。 由于采用了启发式合并,因此时间复杂度为O(nlogn+Qlog^2n)O(nlogn+Qlog2n)O(nlogn+Qlogn)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;
}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值