1. 程式人生 > >BZOJ4408: [Fjoi 2016]神秘數

BZOJ4408: [Fjoi 2016]神秘數

很快 unique size bre tdi \n truct span endif

BZOJ

題意

給你\(n\)個數\(A_i...A_n\),每次詢問一個區間\([l,r]\),問這個區間中的數不能組成的最小正整數是多少;

題解

遇到這種題詢問的題一般先考慮如果只詢問一次,一次詢問整個數列怎麽做;
考慮把數從小到大排序,然後求出前綴和\(S_1...S_n\)接下來有一個比較顯然但是非常關鍵的結論:不能表示的最小的正整數,一定是某個位置的前綴和+1(包含\(0\)\(n\));設\(F[i]=[1,0]\)表示小於等於\(i\)的正整數能否全部被表示出來,那麽我們要找的就是這麽一個位置:最小的\(j\)使得\(F[A_j]=0\),那麽不能表示出的最小正整數便是\(S_{j-1}+1\)

;考慮如果某個位置\(t\),\(F[A_t]=1\),那麽\(S_t\)一定大於等於\(A_t\),那麽這前\(t\)個數一定能夠把\(1,2,3..S_t\)全部表示出來(具體證明只需要把數拆成二進制位看看就很清楚了),那麽轉移就有了兩種情況,如果\(A_{t+1}>S_t+1\)\(F[A_{t+1}]=0\),否則\(F[A_{t+1}]=1\);知道了轉移我們便有了一種很快找到這個位置的辦法了,考慮初始能表示的數為\(s\),\(s\)一開始等於\(0\),然後接下來小於等於\(s+1\)的數全部能被表示出來,找到最多能擴展到的位置\(p\),那麽現在全部能被表示出來的數便成了\(S_p\)
(前綴和),這樣每次去擴展直到擴展不動,最終的答案便是能擴展到的位置的前綴和+1;因為每次會出現一個新的最小的能被表示的數至少比已知能擴展的大\(1\),那麽最壞情況也只會擴展\(log\)次(考慮序列\(1,2,4,8,16..\)每次只擴展一個位置,但因為題目條件所有數總和小於等於\(1e9\),所以最多擴展30次也就能全部擴展完了);
然後是區間查詢,其實我們需要的也就是查區間中有多少個數小於等於某數,上主席樹就做完了;

#include<bits/stdc++.h>
#define Fst first
#define Snd second
#define RG register
#define mp make_pair
#define mem(a,b) memset(a,b,sizeof(a))
using namespace std;
typedef long long LL;
typedef long double LD;
typedef unsigned int UI;
typedef unsigned long long ULL;
template<typename T> inline void read(T& x) {
    char c = getchar();
    bool f = false;
    for (x = 0; !isdigit(c); c = getchar()) {
        if (c == ‘-‘) {
            f = true;
        }
    }
    for (; isdigit(c); c = getchar()) {
        x = x * 10 + c - ‘0‘;
    }
    if (f) {
        x = -x;
    }
}
template<typename T, typename... U> inline void read(T& x, U& ... y) {
    read(x), read(y...);
}
const int N=1e5+10;
int n,Q,SUM,CNT,size;
int A[N],V[N],root[N];
map<int,int> S;
struct Node {
    int lo,ro,cnt,sum;
}Tr[N*20];
void Modify(int l,int r,int &o,int pos,int v) {
    Tr[++size]=Tr[o]; o=size;
    ++Tr[o].cnt; Tr[o].sum+=v;
    if(l==r) return;
    int mid=l+r>>1;
    if(pos<=mid) Modify(l,mid,Tr[o].lo,pos,v);
    else Modify(mid+1,r,Tr[o].ro,pos,v);
}
void Find(int l,int r,int x,int y,int sum) {
    if(l==r) {
        if(V[l]<=sum) SUM+=Tr[x].sum-Tr[y].sum,CNT+=Tr[x].cnt-Tr[y].cnt;
        return;
    }
    int mid=l+r>>1;
    if(V[mid]<=sum) SUM+=Tr[Tr[x].lo].sum-Tr[Tr[y].lo].sum,CNT+=Tr[Tr[x].lo].cnt-Tr[Tr[y].lo].cnt,Find(mid+1,r,Tr[x].ro,Tr[y].ro,sum);
    else Find(l,mid,Tr[x].lo,Tr[y].lo,sum);
}
int main() {
//  ios::sync_with_stdio(false);
#ifdef rua
    freopen("GG.in","r",stdin);
#endif
    read(n);
    for(int i=1;i<=n;++i) read(A[i]),V[i]=A[i];
    sort(V+1,V+n+1);
    int cnt=unique(V+1,V+n+1)-V-1;
    for(int i=1;i<=cnt;++i) S[V[i]]=i;
    for(int i=1;i<=n;++i) Modify(1,cnt,root[i]=root[i-1],S[A[i]],A[i]);
    read(Q);
    while(Q--) {
        int l,r; read(l,r);
        int last=0,sum=0;
        while(1) {
            CNT=SUM=0; Find(1,cnt,root[r],root[l-1],sum+1);
            if(CNT==last) break;
            last=CNT; sum=SUM;
        }
        printf("%d\n",sum+1);
    }
    return 0;
}

BZOJ4408: [Fjoi 2016]神秘數