區間第k大的幾種解法


區間第K大問題,變化包括帶修改和不帶修改,強制在線和允許離線

修改主要是單點修改,我們前面也只討論這種情況。

接下來我們從編程復雜度和時空復雜度來討論幾種做法。

 

1.整體二分(編程復雜度:低-中,時間復雜度:優秀,空間復雜度:優秀)

缺點:只能做離線

優點:空間都是O(n)。常數小。帶修改O(nlog2n),不帶修改O(nlogn)。

但是不帶修改的情況,如果允許的話,個人認為加個樹狀數組寫O(nlog2n)的更好寫

這時單次solve中面對的問題是,數列中一些點是1其余都是0,然后求區間和的問題

這個問題把區間和變成前綴和相減,然后用vector來對那些需要求前綴的點做桶排

然后雙指針即可做到單次solve為O(k),k為此次處理的操作數,總體O(nlogn)的復雜度

顯然如果不差那log的時間,直接用樹狀數組來處理更好寫

下面給了一個帶單點修改查詢區間第K的整體二分代碼

 1 #include <bits/stdc++.h>
 2 
 3 #define lb(x) (x&(-x))
 4 
 5 using namespace std;
 6 
 7 const int N = 2e5 + 5;
 8 
 9 int t, n, m, k, cnt;
10 
11 struct node {
12     int id, i, j, k;
13 }a[N], q1[N], q2[N];
14 
15 int ans[N], b[N];
16 
17 int c[N];
18 
19 char op[5];
20 
21 void add(int i, int x) {while (i <= n) c[i] += x, i += lb(i);}
22 
23 int ask(int i) {int res = 0; while (i > 0) res += c[i], i -= lb(i); return res;}
24 
25 void solve(int head, int tail, int l, int r) {
26     if (head > tail) return;
27     if (l == r) {
28         for (int i = head; i <= tail; i ++)
29             ans[a[i].id] = r;
30         return;
31     }
32     int mid = l + r >> 1, s1 = 0, s2 = 0;
33     for (int sum, i = head; i <= tail; i ++)
34         if (a[i].id) {
35             sum = ask(a[i].j) - ask(a[i].i - 1);
36             if (sum >= a[i].k) q1[s1 ++] = a[i];
37             else a[i].k -= sum, q2[s2 ++] = a[i];
38         }
39         else {
40             if (a[i].j <= mid) q1[s1 ++] = a[i], add(a[i].i, a[i].k);
41             else q2[s2 ++] = a[i];
42         }
43     for (int i = 0; i < s1; i ++)
44         if (!q1[i].id)
45             add(q1[i].i, -q1[i].k);
46     memcpy(a + head, q1, sizeof(node) * s1);
47     memcpy(a + head + s1, q2, sizeof(node) * s2);
48     solve(head, head + s1 - 1, l, mid);
49     solve(head + s1, tail, mid + 1, r);
50 }
51 
52 int main() {
53     ios::sync_with_stdio(false);
54     for (cin >> t; t --; ) {
55         cin >> n >> m; k = cnt = 0;
56         for (int i = 1; i <= n; i ++) {
57             cin >> b[i];
58             a[++ k] = (node){0, i, b[i], 1};
59         }
60         for (int l, r, x, i = 1; i <= m; i ++) {
61             cin >> op >> l >> r;
62             if (op[0] == 'Q') {
63                 cin >> x;
64                 a[++ k] = (node){++ cnt, l, r, x};
65             }
66             else {
67                 a[++ k] = (node){0, l, b[l], -1};
68                 a[++ k] = (node){0, l, b[l] = r, 1};
69             }
70         }
71         solve(1, k, 1, 1e9);
72         for (int i = 1; i <= cnt; i ++)
73             printf("%d\n", ans[i]);
74     }
75     return 0;
76 }
View Code

 

2.主席樹(編程復雜度:低-中,時間復雜度:優秀,空間復雜度:高)

缺點:空間占用多。樹套樹的常數。

優點:可以在線!時間復雜度同整體二分。空間復雜度和時間復雜度一致。

不帶修改是主席樹基本操作。帶修改就套樹狀數組,下面給出(和上面同一個問題的)代碼。

 1 #include <bits/stdc++.h>
 2 
 3 using namespace std;
 4 
 5 const int MAXN = 1e9;
 6 const int N = 5e4 + 5;
 7 
 8 int n, m, a[N], rt[N];
 9 
10 int tot, tr[N * 800][3];
11 
12 int tmp1[100], tmp2[100];
13 
14 #define l(x) tr[x][0]
15 #define r(x) tr[x][1]
16 #define s(x) tr[x][2]
17 #define lb(x) (x&(-x))
18 #define mid (l + r >> 1)
19 
20 int change(int o, int l, int r, int k, int v) {
21     int x = ++ tot; s(x) = s(o) + v;
22     if (l == r) return x; l(x) = l(o), r(x) = r(o);
23     k > mid ? r(x) = change(r(o), mid + 1, r, k, v) : l(x) = change(l(o), l, mid, k, v);
24     return x;
25 }
26 
27 void modify(int i, int p, int v) {
28     while (i <= n) rt[i] = change(rt[i], 1, MAXN, p, v), i += lb(i);
29 }
30 
31 int ask(int l, int r, int k) {
32     if (l == r) return r; int sum = 0;
33     for (int i = 1; i <= tmp1[0]; i ++) sum -= s(l(tmp1[i]));
34     for (int i = 1; i <= tmp2[0]; i ++) sum += s(l(tmp2[i]));
35     if (k > sum) {
36         for (int i = 1; i <= tmp1[0]; i ++) tmp1[i] = r(tmp1[i]);
37         for (int i = 1; i <= tmp2[0]; i ++) tmp2[i] = r(tmp2[i]);
38         return ask(mid + 1, r, k - sum);
39     }
40     else {
41         for (int i = 1; i <= tmp1[0]; i ++) tmp1[i] = l(tmp1[i]);
42         for (int i = 1; i <= tmp2[0]; i ++) tmp2[i] = l(tmp2[i]);
43         return ask(l, mid, k);
44     }
45 }
46 
47 int query(int l, int r, int k) {//查詢區間第k小
48     tmp1[0] = tmp2[0] = 0;
49     for (int i = l - 1; i > 0; i -= lb(i)) tmp1[++ tmp1[0]] = rt[i];
50     for (int i = r;     i > 0; i -= lb(i)) tmp2[++ tmp2[0]] = rt[i];
51     return ask(1, MAXN, k);
52 }
53 
54 int main(){
55     int t; char op[5];
56     for (cin >> t; t --; ) {
57         cin >> n >> m; tot = 0;
58         for (int i = 1; i <= n; i ++) cin >> a[i], rt[i] = 0;
59         for (int i = 1; i <= n; i ++) modify(i, a[i], 1);
60         for (int i, j, k; m --; ) {
61             cin >> op >> i >> j;
62             if (op[0] == 'C') modify(i, a[i], -1), modify(i, a[i] = j, 1);
63             else cin >> k, printf("%d\n", query(i, j, k));
64         }
65     }
66     return 0;
67 }
View Code

 

其他做法我參考了一下,很難與上述兩種做法並肩,就不做討論了

如果有的話歡迎告訴我

 

拓展1.初始數列每個位置都是一個空隊列,修改變成了區間[l,r]的每個隊列末尾都push一個數x

          查詢某個區間所有數字中的第K大

解法:其實還是個整體二分的簡單題目,用線段樹維護即可,復雜度依然是O(nlog2n)


免責聲明!

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



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