1. 程式人生 > >題解 洛谷P1903/BZOJ2120【[國家集訓隊]數顏色 / 維護隊列】

題解 洛谷P1903/BZOJ2120【[國家集訓隊]數顏色 / 維護隊列】

efi zoj return esp fin while 結構體 line 參考

對於不會樹套樹、主席樹的本蒟蒻,還是老老實實的用莫隊做吧....

其實這題跟普通莫隊差不了多遠,無非就是有了一個時間,當我們按正常流程排完序後,按照基本的莫隊來,做莫隊時每次循環對於這一次操作,我們在結構體中記錄一下這次操作前有多少個改值操作,然後將當前的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\n",Ans[i]);
    return 0;
}

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

** 參考隔壁機房 叉ZY 大佬的文章,在此鳴謝叉ZY大佬

~我居然因為少了個等於號調了一個多小時.....~

題解 洛谷P1903/BZOJ2120【[國家集訓隊]數顏色 / 維護隊列】