整體二分(poj 2104)

所謂整體二分,需要數據結構題滿足以下性質:

  1. 詢問的答案具有可二分性
  2. 修改對判定答案的貢獻相對獨立,修改之間互不影響效果
  3. 修改如果對判定答案有貢獻,則貢獻爲一確定的與判定標準無關的值
  4. 貢獻滿足交換律,結合律,具有可加性
  5. 題目允許離線操作

不妨先來考慮下一個簡單易懂的?(?????)的排序算法(?爲數值範圍)
這個方法是自己在思考整體二分的時候??的 雖然在實際應用上沒什麼意義 但是有助於理解整體二分的分治過程
我們假設當前處理的區間裏最小值不小於? 最大值不大於? 令???=(?+?)/2
然後把當前區間掃描一遍 如果一個數不大於???就放到左子區間 否則放到右子區間
如此下去 直到區間內只剩一個數或者? 與 ?相等 排序就完成了

現在回到靜態區間第?小問題 和剛纔那個排序算法類似 我們先二分一個答案???,如果區間內小於等於???的數的個數(記爲???)不超過? 那麼最終答案顯然也是不超過???的,這類詢問我們把它們劃分到左子區間。而對於???大於?的 我們則把它們劃分到右子區間 並且把?減去???,換句話說就是把小於等於???的數的貢獻全部算上後之後就不用考慮了。
可以發現這樣劃分的層數是???? 而每一層的詢問個數是?個 再加上算貢獻時用到的??? 所以複雜度是?(?????????)

以下是poj 2104的參考代碼:

#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <map>
#include <queue>
#include <set>
#include <stack>
#include <string>
#include <vector>

using namespace std;
#define mem(a, b) memset(a, b, sizeof(a))
#define PI acos(-1)
#define debug(a) cout << (a) << endl
typedef long long ll;
int dir8[8][2] = { { 1, 0 }, { 0, 1 }, { -1, 0 }, { 0, -1 }, { 1, 1 }, { 1, -1 }, { -1, 1 }, { -1, -1 } };
int dir4[4][2] = { 1, 0, 0, 1, -1, 0, 0, -1 };
const int INF = 0x3f3f3f3fLL;
const long long LLF = 0x3f3f3f3f3f3f3f3fLL;
const int MAXn = 2e5 + 15;
const int mod = 1e9 + 7;
//priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> q;
int n, m, c[MAXn], ans[MAXn],cnt; //c 樹狀數組, ans:答案
struct node {
    int ty, pos, x, y, k; //ty區別輸入和詢問, pos :下標,x:左,y:右, k:第k小
} q[MAXn], q1[MAXn], q2[MAXn]; //q存儲 ,q1:左區間,q2:右區間

int lowbit(int x)  // 求x最低位的1
{
    return x & -x;
}
void add(int x, int val) //更新樹狀數組
{
    while (x <= n) {
        c[x] += val;
        x += lowbit(x);
    }
}
int sum(int x) //求 1-x 的樹狀數組的值的和
{
    int sum = 0;
    while (x>0) {
        sum += c[x];
        x -= lowbit(x);
    }
    return sum;
}
void solve(int l, int r, int L, int R) 
{
    if (l > r || L > R) 
        return;
    if (L == R) { //如果相等 更新答案
        for (int i = l; i <= r; i++) {
            if (q[i].ty) {
                ans[q[i].pos] = L;
            }
        }
        return;
    }
    int mid = (L + R) >> 1, cnt1 = 0, cnt2 = 0; 
    for (int i = l; i <= r; i++) {
        if (q[i].ty) {//如果是詢問
            int temp = sum(q[i].y) - sum(q[i].x - 1); //temp :q[i].x 到q[i].y 小於等於mid 的個數 
            if (temp >= q[i].k) //如果小於等於mid的數大於k
                q1[++cnt1] = q[i];//放到左區間
            else { //反之 k減去temp 並放到右區間
                q[i].k -= temp;
                q2[++cnt2] = q[i];
            }
        } else { //如果是輸入
            if(q[i].x<=mid){ //如果小於mid
                add(q[i].pos,q[i].y);// 更新樹狀數組
                q1[++cnt1]=q[i];// 放到左區間
            }
            else q2[++cnt2]=q[i];//反之,放到右區間
        }
    }
    for(int i=1;i<=cnt1;i++){
        if(!q1[i].ty){ //更新樹狀數組
             add(q1[i].pos,-q1[i].y);
        }
    }//更新 q 數組
    for(int i=1;i<=cnt1;i++){
        q[l+i-1]=q1[i]; 
    }
    for(int i=1;i<=cnt2;i++){
        q[l+i+cnt1-1]=q2[i]; 
    }
    solve(l,l+cnt1-1,L,mid);//左區間
    solve(l+cnt1,r,mid+1,R);//右區間
    return ;
}
int main(){
//輸入
    cin>>n>>m;
    int l,r,k;
    for(int i=1;i<=n;i++){
        cin>>k;
        q[++cnt]=node{0,i,k,1,0}; // ty==0 表示是輸入
    }
    for(int i=1;i<=m;i++){
        cin>>l>>r>>k;
        q[++cnt]=node{1,i,l,r,k}; //ty==1 表示是詢問 
    }
    solve(1,cnt,-INF,INF);
    for(int i=1;i<=m;i++)//輸出
        cout<<ans[i]<<endl;
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章