整體二分初探 兩類區間第K大問題 poj2104 & hdu5412


看到好多講解都把整體二分和$CDQ$分治放到一起講 不過自己目前還沒學會$CDQ$分治 就單獨談談整體二分好了

先推薦一下$XHR$的 <淺談數據結構題的幾個非經典解法> 整體二分在當中有較為詳細的講解

 

先來說一下靜態第$K$小的整體二分解法 $(POJ2104)$

題目鏈接:http://poj.org/problem?id=2104

所謂整體二分 就是利用所有的詢問相互獨立而把它們$($此題沒有修改操作$)$通過二分把它們分治進行處理

 

不妨先來考慮下一個簡單易懂的$O(NlogS)$的排序算法$(S$為數值范圍$)$

這個方法是自己在思考整體二分的時候$yy$的 雖然在實際應用上沒什么意義 但是有助於理解整體二分的分治過程

我們假設當前處理的區間里最小值不小於$L$ 最大值不大於$R$ 令$MID = (L + R) / 2$

然后把當前區間掃描一遍 如果一個數不大於$MID$就放到左子區間 否則放到右子區間

如此下去 直到區間內只剩一個數或者$L$ 與 $R$相等 排序就完成了

 

現在回到靜態區間第$K$小問題 和剛才那個排序算法類似 我們先二分一個答案$MID$

如果區間內小於等於$MID$的數的個數$($記為$CNT)$不超過$K$ 那么最終答案顯然也是不超過$MID$的

這類詢問我們把它們划分到左子區間

而對於$CNT$大於$K$的 我們則把它們划分到右子區間 並且把$K$減去$CNT$

換句話說就是把小於等於$MID$的數的貢獻全部算上后之后就不用考慮了

可以發現這樣划分的層數是$logS$ 而每一層的詢問個數是$Q$個 再加上算貢獻時用到的$BIT$ 所以復雜度是$O(QlogNlogS)$

 

以下是$poj2104$參考代碼

 1 //#include <bits/stdc++.h>
 2 #include <cstdio>
 3 #include <cstring>
 4 #include <cmath>
 5 #include <algorithm>
 6 using namespace std;
 7 const int N = 1e5 + 10, Q = 5e3 + 10, lim = 1e9;
 8 struct point
 9 {
10     int x, num;
11 }a[N];
12 struct query
13 {
14     int x, y, k, cnt, num;
15 }q[Q], b[Q];
16 int sum[N], ans[Q];
17 int n, m;
18 bool cmp(const point &aa, const point &bb)
19 {
20     return aa.x < bb.x;
21 }
22 void calc(int ll, int rr, int rawL, int mid)
23 {
24     int L = 1, R = n + 1, MID;
25     while(L < R)
26     {
27         MID = (L + R) >> 1;
28         if(a[MID].x >= rawL)
29             R = MID;
30         else
31             L = MID + 1;
32     }
33     for(int i = R; i <= n && a[i].x <= mid; ++i)
34         for(int j = a[i].num; j <= n; j += (j & -j))
35             ++sum[j];
36     for(int i = ll; i <= rr; ++i)
37     {
38         q[i].cnt = 0;
39         for(int j = q[i].y; j; j -= (j & -j))
40             q[i].cnt += sum[j];
41         for(int j = q[i].x - 1; j; j -= (j & -j))
42             q[i].cnt -= sum[j];
43     }
44     for(int i = R; i <= n && a[i].x <= mid; ++i)
45         for(int j = a[i].num; j <= n; j += (j & -j))
46             --sum[j];
47 }
48 void divide(int ll, int rr, int rawL, int rawR)
49 {
50     if(rawL == rawR)
51     {
52         for(int i = ll; i <= rr; ++i)
53             ans[q[i].num] = rawR;
54         return;
55     }
56     int mid = rawL + ((rawR - rawL) >> 1);
57     calc(ll, rr, rawL, mid);
58     int now1 = ll, now2 = rr;
59     for(int i = ll; i <= rr; ++i)
60     {
61         if(q[i].cnt >= q[i].k)
62             b[now1++] = q[i];
63         else
64         {
65             q[i].k -= q[i].cnt;
66             b[now2--] = q[i];
67         }
68     }
69     for(int i = ll; i <= rr; ++i)
70         q[i] = b[i];
71     if(now1 != ll)
72         divide(ll, now1 - 1, rawL, mid);
73     if(now2 != rr)
74         divide(now2 + 1, rr, mid + 1, rawR);
75 }
76 int main()
77 {
78     scanf("%d%d", &n, &m);
79     for(int i = 1; i <= n; ++i)
80     {
81         scanf("%d", &a[i].x);
82         a[i].num = i;
83     }
84     a[n + 1].x = 2e9;
85     sort(a + 1, a + 1 + n, cmp);
86     for(int i = 1; i <= m; ++i)
87     {
88         scanf("%d%d%d", &q[i].x, &q[i].y, &q[i].k);
89         q[i].num = i;
90     }
91     divide(1, m, -lim, lim);
92     for(int i = 1; i <= m; ++i)
93         printf("%d\n", ans[i]);
94     return 0;
95 }

 

然后就是整體二分真正突出存在意義的問題 動態區間第$K$大了$(hdu5412)$

題目鏈接:http://acm.hdu.edu.cn/showproblem.php?pid=5412

動態區間第$K$大就是額外多了修改操作 修改不僅對詢問有影響 對修改也有影響 因此直觀上看起來要麻煩很多

 

我們知道 區間詢問之所以能排序后分治 主要是所有的詢問相互獨立

對於修改操作 如果我們希望它們相互獨立該怎么做呢 那就算貢獻好了

實際上  每次把一個位置的數修改為另一個數相當於使這個位置刪除一個原來的數 並插入一個新的數 而初始的數則相當於僅有插入操作

這樣只要保證同一區間內的查詢和刪除或插入操作的相對順序不變 即相對時間順序不變 最后算貢獻結果也是一樣的

並且由於對大於$MID$的數的刪除或插入操作 即便時間順序是在划分到左子區間的詢問之前 也不會造成影響 因此它們可以划分到右子區間

而對小於$MID$的數的刪除或插入操作 對划分到右子區間的詢問的影響可以直接在划分前計入貢獻之后不再考慮 因此它們可以划分到左子區間

這樣按照類似靜態區間第$K$大問題計算復雜度會發現是$O((Q+N)logNlogS)$

 

以下是$hdu5412$的參考代碼

  1 #include <bits/stdc++.h>
  2 using namespace std;
  3 const int N = 1e5 + 10, Q = 1e5 + 10, A = N + Q * 2, lim = 1e9;
  4 struct operate
  5 {
  6     int x, y, k, cnt, num;
  7     operate(){}
  8     operate(int _x, int _y, int _k, int _cnt, int _num)
  9     {
 10         x = _x;
 11         y = _y;
 12         k = _k;
 13         cnt = _cnt;
 14         num = _num;
 15     }
 16 }a[A], q1[A], q2[A];
 17 int raw[N], ans[Q], sum[N];
 18 int n, m, len, l1, l2;
 19 void update(int x, int y)
 20 {
 21     for(int i = x; i <= n; i += (i & -i))
 22         sum[i] += y;
 23 }
 24 int query(int x)
 25 {
 26     int re = 0;
 27     for(int i = x; i; i -= (i & -i))
 28         re += sum[i];
 29     return re;
 30 }
 31 void calc(int ll, int rr, int rawl, int mid)
 32 {
 33     for(int i = ll; i <= rr; ++i)
 34     {
 35         if(a[i].k)
 36             a[i].cnt = query(a[i].y) - query(a[i].x - 1);
 37         else if(a[i].y <= mid)
 38             update(a[i].x, a[i].cnt);
 39     }
 40     for(int i = ll; i <= rr; ++i)
 41         if(!a[i].k && a[i].y <= mid)
 42             update(a[i].x, -a[i].cnt);
 43     l1 = l2 = 0;
 44     for(int i = ll; i <= rr; ++i)
 45         if(a[i].k)
 46         {
 47             if(a[i].k <= a[i].cnt)
 48                 q1[++l1] = a[i];
 49             else
 50             {
 51                 a[i].k -= a[i].cnt;
 52                 q2[++l2] = a[i];
 53             }
 54         }    
 55         else
 56         {
 57             if(a[i].y <= mid)
 58                 q1[++l1] = a[i];
 59             else
 60                 q2[++l2] = a[i];
 61         }
 62     int now = ll;
 63     for(int i = 1; i <= l1; ++i)
 64         a[now++] = q1[i];
 65     for(int i = 1; i <= l2; ++i)
 66         a[now++] = q2[i];
 67 }
 68 void divide(int ll, int rr, int rawl, int rawr)
 69 {
 70     if(rawl == rawr)
 71     {
 72         for(int i = ll; i <= rr; ++i)
 73             if(a[i].k)
 74                 ans[a[i].num] = rawl;
 75         return;
 76     }
 77     int mid = (rawl + rawr) >> 1;
 78     calc(ll, rr, rawl, mid);
 79     int tmp = l1;
 80     if(tmp)
 81         divide(ll, ll + tmp - 1, rawl, mid);
 82     if(ll + tmp <= rr)
 83         divide(ll + tmp, rr, mid + 1, rawr);
 84 }
 85 int main()
 86 {
 87     while(scanf("%d", &n) != EOF)
 88     {
 89         len = 0;
 90         for(int i = 1; i <= n; ++i)
 91         {
 92             scanf("%d", &raw[i]);
 93             a[++len] = operate(i, raw[i], 0, 1, 0);
 94          }
 95          scanf("%d", &m);
 96          int op, x, y, z;
 97          for(int i = 1; i <= m; ++i)
 98          {
 99             scanf("%d", &op);
100             if(op & 1)
101             {
102                 scanf("%d%d", &x, &y);
103                 a[++len] = operate(x, raw[x], 0, -1, 0);
104                 a[++len] = operate(x, y, 0, 1, 0);
105                 raw[x] = y;
106                 ans[i] = 0;
107             }
108             else
109             {
110                 scanf("%d%d%d", &x, &y, &z);
111                 a[++len] = operate(x, y, z, 0, i);
112             }
113         }
114         divide(1, len, 1, lim);
115         for(int i = 1; i <= m; ++i)
116             if(ans[i])
117                 printf("%d\n", ans[i]);
118     }
119     return 0;
120 }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM