1. 程式人生 > >[Ynoi2016]掉進兔子洞 題解

[Ynoi2016]掉進兔子洞 題解

題面傳送門:https://www.luogu.org/problemnew/show/P4688
(溫馨提示,請直接翻至題目描述部分)

1e5的資料範圍,以及對區間每個權值出現次數取min此類主席樹才能解決的操作會讓我們想到莫隊;
三個區間取交集的操作會讓我們想到bitset。
然而同個數值在多個位置出現需要分別計算,這點讓我們對於使用bitset產生了懷疑。
但實際上我們可以利用一個小trick來解決這個問題,那就是利用不尋常的離散化,就是sort之後不用unique,讓bitset每一位不只代表數值,還代表這個數值出現的次數。
這樣我們莫隊的時候,對於當前區間維護一個桶cnt,記錄每個離散化後的權值在此區間出現的次數,和一個bitset,維護權值及權值出現的次數資訊。然後三個區間的bitset取交,再用三個區間的長度和減去交集1的個數就是答案。

具體實現請看程式碼:

#include<bits/stdc++.h>
using namespace std;
#define rep(i,a,b) for(register int i=(a);i<=(b);++i)
const int N =100505;
const int M =33350;
int n,m,blo[N],K,a[N],R;
struct la{int l,r,id;}ask[N];
bitset<N> ans[M],now;
int cnt[N],p[N],tot;
bool cmp(la x,la y){
    return blo[x.l]==blo[y.l]?x.r<y.r:blo[x.l]<blo[y.l];
}
inline void del(int x){cnt[x]--;now[x-cnt[x]]=0;}
inline void add(int x){now[x-cnt[x]]=1;cnt[x]++;}
void doit(int y){
    tot=0;
    rep(i,1,y)p[i]=0;
    rep(i,1,y){
        ++tot;scanf("%d%d",&ask[tot].l,&ask[tot].r);p[i]+=ask[tot].r-ask[tot].l+1;
        ++tot;scanf("%d%d",&ask[tot].l,&ask[tot].r);p[i]+=ask[tot].r-ask[tot].l+1;
        ++tot;scanf("%d%d",&ask[tot].l,&ask[tot].r);p[i]+=ask[tot].r-ask[tot].l+1;
        ask[tot].id=ask[tot-1].id=ask[tot-2].id=i;
    }
    sort(ask+1,ask+tot+1,cmp);
    int l=1,r=0;
    rep(i,1,y)ans[i].set();//不用帶參,直接全置為 1
    rep(i,1,tot){
        while(ask[i].l<l)add(a[--l]);
        while(ask[i].r>r)add(a[++r]);
        while(ask[i].l>l)del(a[l++]);
        while(ask[i].r<r)del(a[r--]);
        ans[ask[i].id]&=now;
    }
    while(l<=r)del(a[l++]);
    rep(i,1,y)printf("%d\n",p[i]-3*(int)ans[i].count());
}
int main(){
    scanf("%d%d",&n,&m);K=sqrt(n)+1;
    rep(i,1,n)scanf("%d",&a[i]),p[i]=a[i],blo[i]=(i-1)/K+1;
    sort(p+1,p+n+1);
    rep(i,1,n)a[i]=upper_bound(p+1,p+n+1,a[i])-p-1;
    R=m/3;
    if(R)doit(R);
    if(R)doit(R);
    doit(m-2*R);
    return 0;
}