zoukankan      html  css  js  c++  java
  • 题解 洛谷P1903/BZOJ2120【[国家集训队]数颜色 / 维护队列】

    对于不会树套树、主席树的本蒟蒻,还是老老实实的用莫队做吧....

    其实这题跟普通莫队差不了多远,无非就是有了一个时间,当我们按正常流程排完序后,按照基本的莫队来,做莫队时每次循环对于这一次操作,我们在结构体中记录一下这次操作前有多少个改值操作,然后将当前的ans和每个点的颜色信息更新至当前队列(此队列不是莫队,是原队列)的状态再移动l,r两指针。

    简单点说:

    设当前询问为a,下一个询问为b,我们已知a,要求b。

    首先我们像普通莫队一样转移左右端点。

    这时候我们可能会发现a和b的经历的修改次数不同

    假如a较b少修改了p次,那我们就把这p次修改一个一个从前往后暴力地加上去。假如a较b多修改了q次,那我们就把这q次修改从后往前还原掉。

    还要注意一点:

    带修改的莫队的询问排序方法为:

    • 第一关键字:左端点所在块编号,从小到大排序。
    • 第二关键字:右端点所在块编号,从小到大排序。
    • 第三关键字:经历的修改次数。也可以说是询问的先后,先询问的排前面。

    注意的几点:

    • 时间上的优化:将块的大小从sqrt(n)改为n的二分之三次方
    • 可修改莫队只用于单点修改,区间修改的题目就算了吧...
    • 复杂度大约为O(n的五分之三次方)
    • 一样是离线操作

    但是具体怎么操作呢?

    我们先看一下结构体:

    struct Node{
    	int l,r,c,id;
    	bool operator < (const Node a)const {
    	    if(l/Be==a.l/Be){
                if(r/Be==a.r/Be)return id<a.id;
                return r<a.r;
            }return l<a.l;
    	}
    }q[N];
    

    上面的重载运算符就是按照上文的排序方式来写的。

    结构体中l,r分别是询问的左右端点,c是该询问之前的修改操作次数,id是这个操作的位置。

    再来比较一下普通的莫队操作和可修改的莫队操作:

    更新ans的函数(一样的):

    inline void Add(int x){if(!sum[x])ans++;sum[x]++;}
    inline void Del(int x){sum[x]--;if(!sum[x])ans--;}
    

    普通莫队:

    for(register int i=1;i<=m;++i){
        while(l>q[i].l)l--,Add(val[l]);
        while(r<q[i].r)r++,Add(val[r]);
        while(l<q[i].l)Add(val[l]),l++;
        while(r>q[i].r)Add(val[r]);,r--;
        Ans[q[i].id]=ans;
    }
    

    可修改莫队:

    for(register int i=0;i<c1;++i){
        for(;lst<q[i].c;lst++){
        	if(l<=Q[lst][0]&&Q[lst][0]<=r)
        	   Del(Q[lst][1]),Add(Q[lst][2]);
        	val[Q[lst][0]]=Q[lst][2];
        }
        for(;lst>q[i].c;lst--){
        	if(l<=Q[lst-1][0]&&Q[lst-1][0]<=r)
        	   Del(Q[lst-1][2]),Add(Q[lst-1][1]); 
        	val[Q[lst-1][0]]=Q[lst-1][1];
        }
        for(++r;r<=q[i].r;r++)Add(val[r]);
        for(--r;r>q[i].r;r--)Del(val[r]);
        for(--l;l>=q[i].l;l--)Add(val[l]);
        for(++l;l<q[i].l;l++)Del(val[l]);
        Ans[q[i].id]=ans;
    }
    

    (注:可修改莫队的下面四个for循环,是在我的心态极不好的时候才从让我WA了几遍的while循环改过来的,但是后面才发现不是while的原因......贴代码过来时不想改了)

    我们发现,可修改莫队多了一段:

    for(;lst<q[i].c;lst++){
        if(l<=Q[lst][0]&&Q[lst][0]<=r)
           Del(Q[lst][1]),Add(Q[lst][2]);
        val[Q[lst][0]]=Q[lst][2];
    }
    for(;lst>q[i].c;lst--){
        if(l<=Q[lst-1][0]&&Q[lst-1][0]<=r)
           Del(Q[lst-1][2]),Add(Q[lst-1][1]); 
        val[Q[lst-1][0]]=Q[lst-1][1];
    }
    

    没错!这就是这段话:

    假如a较b少修改了p次,那我们就把这p次修改一个一个从前往后暴力地加上去。假如a较b多修改了q次,那我们就把这q次修改从后往前还原掉。

    上面的if是来判断这个改色操作是否在当前莫队范围中,在的话肯定要维护一下当前的莫队ans值嘛,但是不在范围中的话,直接改颜色不就好了......(貌似并没有那么难操作诶)


    • 输入时的操作:

    用一个变量c1表示有多少个询问操作,c2表示有多少个更改操作。

    • 用结构体保存每次询问操作的相关信息
    • 用一个二维数组保存每次更改操作的相关信息

    代码:

    for(int i=1,a,b;i<=m;i++)
       if(scanf("%s",opt),read(a),read(b),opt[0]=='Q')
          q[c1].l=a,q[c1].r=b,q[c1].id=c1,q[c1].c=c2,c1++;
       else Q[c2][0]=a,Q[c2][1]=C[a],Q[c2][2]=C[a]=b,c2++;
    
    • Q[i][0] 表示第i次操作需更改的位置
    • Q[i][1] 表示第i次操作更改位置的现在颜色
    • Q[i][2] 表示第i次操作要改成的颜色
    • 注意,Q[i][1]在之前的更改操作中可能已经被改过,而我们有要记录这个点现在的值,所以我维护了一个C数组来随时记录每次操作的变化,原序列数组为val数组,显然是不能用原序列数组来记录的(不然到莫队时就乱了)

    说了这么多,总算是要贴AC代码了。

    AC代码(请原谅我鬼畜的码风,压行压疯了...):

    #include<bits/stdc++.h>
    #define ll long long
    #define inf 0x3f3f3f3f
    #define A printf("A")
    #define P(x) printf("V %d V",x); 
    #define S 1000003
    using namespace std;
    const int N=5e4+5;
    template<typename _Tp>inline void read(_Tp&dig){
        char c;dig=0;
        while(c=getchar(),!isdigit(c));
        while(isdigit(c))dig=dig*10+c-'0',c=getchar();
    }
    int n,m,Be,c1,c2,ans,C[N],val[N],Ans[N],sum[S],Q[N][3];
    struct Node{
    	int l,r,c,id;
    	bool operator < (const Node a)const {
    	    if(l/Be==a.l/Be){
                if(r/Be==a.r/Be)return id<a.id;
                return r<a.r;
            }return l<a.l;
    	}
    }q[N];char opt[10];
    inline void Add(int x){if(!sum[x])ans++;sum[x]++;}
    inline void Del(int x){sum[x]--;if(!sum[x])ans--;}
    int main(){
        read(n);read(m);Be=pow(n,(double)2/(double)3);
        for(register int i=1;i<=n;++i)read(val[i]),C[i]=val[i];
        for(int i=1,a,b;i<=m;i++)
           if(scanf("%s",opt),read(a),read(b),opt[0]=='Q')
              q[c1].l=a,q[c1].r=b,q[c1].id=c1,q[c1].c=c2,c1++;
           else Q[c2][0]=a,Q[c2][1]=C[a],Q[c2][2]=C[a]=b,c2++;
        sort(q,q+c1),Add(val[1]);int l=1,r=1,lst=0; 
        for(register int i=0;i<c1;++i){
        	 for(;lst<q[i].c;lst++){
        		 if(l<=Q[lst][0]&&Q[lst][0]<=r)
        			Del(Q[lst][1]),Add(Q[lst][2]);
        		 val[Q[lst][0]]=Q[lst][2];
        	 }
        	 for(;lst>q[i].c;lst--){
        		 if(l<=Q[lst-1][0]&&Q[lst-1][0]<=r)
        			Del(Q[lst-1][2]),Add(Q[lst-1][1]); 
        		 val[Q[lst-1][0]]=Q[lst-1][1];
        	 }
        	 for(++r;r<=q[i].r;r++)Add(val[r]);
             for(--r;r>q[i].r;r--)Del(val[r]);
             for(--l;l>=q[i].l;l--)Add(val[l]);
             for(++l;l<q[i].l;l++)Del(val[l]);
        	 Ans[q[i].id]=ans;
        }for(register int i=0;i<c1;++i)printf("%d
    ",Ans[i]);
    	return 0;
    }
    

    提交上去,哇,~跑的真快~慢死了!(好吧,我觉的还需优化一下......)

    ** 参考隔壁机房 叉ZY 大佬的文章,在此鸣谢叉ZY大佬

    ~我居然因为少了个等于号调了一个多小时.....~
  • 相关阅读:
    封装微信小程序
    请求formdata格式
    基于vue的前端框架
    es6 入门
    测试缓存时间问题console.time
    export 与 export default, 以及import引用
    vue emit 实现非父子之间的值传递
    css3 弹性盒子display:flex
    iview $modal 的封装
    render iview tab select的添加和input的添加
  • 原文地址:https://www.cnblogs.com/K-Qiuly/p/10273943.html
Copyright © 2011-2022 走看看