1. 程式人生 > 實用技巧 >【題解】「Ynoi2018」未來日記 [*medium]

【題解】「Ynoi2018」未來日記 [*medium]

查詢 \(kth\) 的話,就分塊來講,通常可以考慮值域分塊。

具體操作就是將值域分塊,然後詢問的時候先確定塊,再確定目標位置。

\(sum_{i,j}\) 表示前 \(i\) 個序列塊,第 \(j\) 個數值塊的出現次數;\(cnt_{i,j}\) 表示前 \(i\) 個序列塊,第 \(j\) 個數的出現次數。

這樣話查詢的時候拿個桶記錄一下散塊貢獻即可。

修改的話其實就是從左端點的塊一直修改到最後面的塊,更新 \(sum,cnt\) 即可。

這下子就有一個問題:當處理散塊時,需要直接訪問 \(a_i\) 。這就意味著 \(a_i\) 必須支援實時查詢。怎麼維護 \(a_i\) 呢?這個時候我們可以想到第二分塊中用並查集維護 "將所有 \(x\)

修改為 \(y\) " 的操作,照搬即可。


關於實現:

  • 程式碼量不算很大,也不怎麼卡常,細節也不是很多。(總之很好寫
  • 注意到 \(c\) 陣列其實是一個 \(n\sqrt{n}\) 大小的陣列:
    • 如果第一維開 \(n\) ,第二維開 \(\sqrt{n}\) :修改的時候記憶體訪問所佔常數太大。
    • 如果第一維開 \(\sqrt{n}\) ,第二維開 \(n\) :這樣的話記憶體訪問就舒服不少。實測快了不少。
  • 並查集的 merge :如果沒有 \(x\) 這個顏色就跳過。實測快了不少。(卡了這個點就過了,之前一直是 \(64\ pts\)
  • 如果硬說細節的話:更新 \(sum,cnt\)
    的時候注意一下更新順序。

吐槽:感覺這題比第二分塊簡單一些(

const int N=1e5+5;
const int S=5e2+5;
const int M=1e5;

int n,m,x,y,a[N];

// {{{ Data_Struct_Block

int sqrtn,L[S],R[S],id[N],mi[S],mx[S],blo[N];
int sum[S][S],cnt[N][S],tmp_sum[S],tmp_cnt[N];

// {{{ Data_Struct_Dsu

int rt[S][N],col[N],fa[N];
int find(int u) {return fa[u]==u?u:fa[u]=find(fa[u]);}

inline void merge(const int &t) {
    if(!rt[t][x]) return ;
    if(!rt[t][y]) rt[t][y]=rt[t][x],col[rt[t][y]]=y;
    else fa[rt[t][x]]=rt[t][y];
    rt[t][x]=0;
}
inline void rebuild(const int &t,int l,int r) {
    lep(i,L[t],R[t]) a[i]=col[find(i)],rt[t][a[i]]=0;
    lep(i,l,r) if(a[i]==x) a[i]=y;

    lep(i,L[t],R[t]) {
        if(!rt[t][a[i]]) col[rt[t][a[i]]=i]=a[i];
        fa[i]=rt[t][a[i]];
    }
}

// }}}

inline void init() {
    sqrtn=sqrt(n*3/5+1);
    for(int i=1,c=1,j;i<=n;i=j+1,++c) { // seq_block
        L[c]=i,R[c]=j=min(n,i+sqrtn-1);
        lep(t,L[c],R[c]) id[t]=c;
    }
    for(int i=1,c=1,j;i<=M;i=j+1,++c) { // num_block
        mi[c]=i,mx[c]=j=min(M,i+315);
        lep(t,mi[c],mx[c]) blo[t]=c;
    }

    lep(c,1,id[n]) { // cnt & sum
        COPY(sum[c],sum[c-1]);
        lep(i,1,M) cnt[i][c]=cnt[i][c-1];
        lep(i,L[c],R[c]) ++cnt[a[i]][c],++sum[c][blo[a[i]]];
    }
    lep(c,1,id[n]) lep(i,L[c],R[c]) { // dsu
        if(!rt[c][a[i]]) col[rt[c][a[i]]=i]=a[i];
        fa[i]=rt[c][a[i]];
    }
}

inline void modify(int l,int r) {
    if(x==y) return ;

    int res=0,tmp=0,v1=0,v2=0;
    #define upd1 (sum[i][blo[x]]-=res,sum[i][blo[y]]+=res)
    #define upd2 (cnt[x][i]-=res,cnt[y][i]+=res)

    if(id[l]==id[r]) {
        lep(i,l,r) if(col[find(i)]==x) ++res;
        if(!res) return ;
        lep(i,id[r],id[n]) upd1,upd2;
        rebuild(id[l],l,r);
    } else {
        lep(i,l,R[id[l]]) if(col[find(i)]==x) ++res,++v1;
        lep(i,id[l],id[r]-1) res+=tmp,upd1,tmp=cnt[x][i+1]-cnt[x][i],upd2;
        lep(i,L[id[r]],r) if(col[find(i)]==x) ++res,++v2;
        lep(i,id[r],id[n]) upd1,upd2;

        lep(i,id[l]+1,id[r]-1) merge(i);
        if(v1) rebuild(id[l],l,R[id[l]]);
        if(v2) rebuild(id[r],L[id[r]],r);
    }
}
inline void query(int l,int r) {
    int tmp;
    const bool _=(id[l]!=id[r]);
    #define add (tmp=col[find(i)],++tmp_cnt[tmp],++tmp_sum[blo[tmp]])
    #define del (tmp=col[find(i)],--tmp_cnt[tmp],--tmp_sum[blo[tmp]])

    if(_) {lep(i,l,R[id[l]]) add; lep(i,L[id[r]],r) add;}
    else lep(i,l,r) add;

    int res=0,ans=-1;
    lep(t,1,blo[M]) {
        int now=tmp_sum[t]+sum[id[r]-_][t]-sum[id[l]][t];
        if(res+now>=x) lep(i,mi[t],mx[t]) {
            res+=tmp_cnt[i]+cnt[i][id[r]-_]-cnt[i][id[l]];
            if(res>=x) {ans=i; break;}
        }
        if(~ans) break; res+=now;
    }
    printf("%d\n",ans);

    if(_) {lep(i,l,R[id[l]]) del; lep(i,L[id[r]],r) del;}
    else lep(i,l,r) del;
}

// }}}

int op,l,r;
int main() {
    IN(n,m);
    lep(i,1,n) IN(a[i]);
    init();

    while(m--) {
        IN(op,l,r);
        if(op==1) IN(x,y),modify(l,r);
        if(op==2) IN(x),query(l,r);
    }
    return 0;
}