挑战程序设计竞赛 3.3 活用各种数据结构

活用各种数据结构

Binary Indexed Tree

POJ 1990  有N头牛,位置x,权值v。两两交谈时要以max(vi, vj)×|xi - xj|的音量。问所有N×(N-1)/2组牛交谈的音量总和

  按 v 排序,后面每加入一只新的牛 i 时,此时它的v最大,所以总是取vi;同时维护两个BIT,都以x为下标,一个维护数量,一个维护坐标,这样可以O(logN)得到所有|xi-xj|。

 1 #include <algorithm>
 2 #include <cstdio>
 3 #include <iostream>
 4 using namespace std;
 5 #define ll long long
 6 #define lowbit(x) x &(-x)
 7 const int N = 2e4 + 5;
 8 
 9 struct cow {
10     ll v, x;
11 } a[N];
12 
13 bool operator<(cow x1, cow x2) { return x1.v < x2.v; }
14 
15 ll n, num[N], sum[N], res, maxx;
16 
17 void add(ll *arr, ll i, ll k) {
18     while (i <= maxx) {
19         arr[i] += k;
20         i += lowbit(i);
21     }
22 }
23 
24 ll getsum(ll *arr, ll i) {
25     ll ans = 0;
26     while (i) {
27         ans += arr[i];
28         i -= lowbit(i);
29     }
30     return ans;
31 }
32 
33 int main() {
34     scanf("%d", &n);
35     for (int i = 0; i < n; i++) {
36         scanf("%lld%lld", &a[i].v, &a[i].x);
37         maxx = max(maxx, a[i].x);
38     }
39     sort(a, a + n);
40     for (int i = 0; i < n; i++) {
41         ll n1 = getsum(num, a[i].x - 1), n2 = i - n1,
42            s1 = getsum(sum, a[i].x - 1), s2 = getsum(sum, maxx) - s1;
43         res += (n1 * a[i].x - s1 + s2 - n2 * a[i].x) * a[i].v;
44         add(num, a[i].x, 1);
45         add(sum, a[i].x, a[i].x);
46     }
47     printf("%lld", res);
48 }
View Code

 

POJ 3109  给出二维平面上N个黑点的坐标,其余格点为白点。当一个白点上下左右(直线方向)上都有黑点时,会变成黑点。问最后有几个黑点。

  先离散化。预处理每个y坐标上最左边的x坐标和最右边的x坐标。按 x一维 y二维 对点排序,当加入一个点是所在y坐标上最左边时,在BIT上+1;是最右边时,BIT上-1。然后加上col[i][j]与col[i][j+1]之间的点数量。

 1 #include <algorithm>
 2 #include <cstdio>
 3 #include <cstring>
 4 #include <iostream>
 5 #include <vector>
 6 using namespace std;
 7 #define ll long long
 8 #define lowbit(x) x &(-x)
 9 const int N = 1e5 + 5;
10 
11 int n, X[N], Y[N], mmin[N], mmax[N], a[N], w, h;
12 vector<int> col[N];
13 
14 int compress(int *x) {
15     vector<int> xs(n);
16     for (int i = 0; i < n; i++) xs[i] = x[i];
17     sort(xs.begin(), xs.end());
18     int nn = 1;
19     for (int i = 1; i < n; i++) {
20         if (xs[nn - 1] < xs[i]) xs[nn++] = xs[i];
21     }
22     for (int i = 0; i < n; i++) {
23         int l = 0, r = nn;
24         while (l < r - 1) {
25             int mid = (l + r) / 2;
26             if (xs[mid] <= x[i])
27                 l = mid;
28             else
29                 r = mid;
30         }
31         x[i] = l + 1;
32     }
33     return nn;
34 }
35 
36 void add(int i, int k) {
37     while (i <= h) {
38         a[i] += k;
39         i += lowbit(i);
40     }
41 }
42 
43 int sum(int i) {
44     int ans = 0;
45     while (i) {
46         ans += a[i];
47         i -= lowbit(i);
48     }
49     return ans;
50 }
51 
52 void pul(int x, int y) {
53     if (x == mmin[y] && x < mmax[y])
54         add(y, 1);
55     else if (mmin[y] < x && x == mmax[y])
56         add(y, -1);
57 }
58 
59 int main() {
60     scanf("%d", &n);
61     for (int i = 0; i < n; i++) {
62         scanf("%d%d", &X[i], &Y[i]);
63     }
64     w = compress(X), h = compress(Y);
65     memset(mmin, 127, sizeof(mmin));
66     for (int i = 0; i < n; i++) {
67         col[X[i]].push_back(Y[i]);
68         mmin[Y[i]] = min(mmin[Y[i]], X[i]);
69         mmax[Y[i]] = max(mmax[Y[i]], X[i]);
70     }
71     ll res = n;
72     for (int i = 1; i <= w; i++) {
73         sort(col[i].begin(), col[i].end());
74         for (int j = 0; j < col[i].size() - 1; j++) {
75             res += sum(col[i][j + 1] - 1) - sum(col[i][j]);
76             pul(i, col[i][j]);
77         }
78         pul(i, col[i].back());
79     }
80     printf("%lld", res);
81 }
View Code

 

POJ 2155  矩阵上单点修改,区间查询。

  裸的二维BIT。 

 1 #include <cstdio>
 2 #include <cstring>
 3 #include <iostream>
 4 using namespace std;
 5 #define ll long long
 6 #define lowbit(x) x&(-x)
 7 const int N = 1e3 + 5;
 8 
 9 int C[N][N], n, q;
10 
11 void add(int x, int y, int k) {
12     for (int i = x; i <= n; i += lowbit(i)) {
13         for (int j = y; j <= n; j += lowbit(j)) {
14             C[i][j] += k;
15         }
16     }
17 }
18 
19 ll sum(int x, int y) {
20     ll ans = 0;
21     for (int i = x; i; i -= lowbit(i)) {
22         for (int j = y; j; j -= lowbit(j)) {
23             ans += C[i][j];
24         }
25     }
26     return ans;
27 }
28 
29 int main() {
30     int T, x1, x2, y1, y2;
31     char c;
32     scanf("%d", &T);
33     while (T--) {
34         memset(C, 0, sizeof(C));
35         scanf("%d%d", &n, &q);
36         for (int i = 0; i < q; i++) {
37             getchar();
38             scanf("%c", &c);
39             if (c == 'C') {
40                 scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
41                 add(x1, y1, 1);
42                 add(x1, y2 + 1, -1);
43                 add(x2 + 1, y1, -1);
44                 add(x2 + 1, y2 + 1, 1);
45             } else {
46                 scanf("%d%d", &x1, &y1);
47                 printf("%lld\n", sum(x1, y1) % 2);
48             }
49         }
50         if (T) printf("\n");
51     }
52 }
View Code

 

POJ 2886  一群小朋友围坐一起,编号1-N,每个人手上一张卡片。从k号开始,卡片号码为ai正数时,指定第ai个下家;否则,指定第-ai个上家,自己出局,由指定玩家继续。第x个出局获得F(x)个糖果(F(x)为x的因子个数),问谁糖果最多。

  预处理因子。BIT维护当前小朋友的在场状态,二分查询下一个要出局的人的编号。

 1 #include <cstdio>
 2 #include <cstring>
 3 #include <iostream>
 4 using namespace std;
 5 #define ll long long
 6 #define lowbit(x) x&(-x)
 7 const int N = 5e5 + 5;
 8 
 9 int n, k;
10 int C[N], num[N], p[N], anti[N];
11 char name[N][15];
12 
13 void add(int i, int k) {
14     while (i <= n) {
15         C[i] += k;
16         i += lowbit(i);
17     }
18 }
19 
20 int sum(int i) {
21     int ans = 0;
22     while (i) {
23         ans += C[i];
24         i -= lowbit(i);
25     }
26     return ans;
27 }
28 
29 int main() {
30     for (int i = 1; i <= 500000; i++) {
31         for (int j = 1; j * j <= i; j++) {
32             if (i % j == 0) {
33                 if (j * j == i)
34                     num[i]++;
35                 else
36                     num[i] += 2;
37             }
38         }
39     }
40     int cnt = 1;
41     for (int i = 1; i <= 500000; i++) {
42         if (num[i] > num[cnt]) cnt = i;
43         anti[i] = cnt;
44     }
45     while (scanf("%d%d", &n, &k) != EOF) {
46         int pos = anti[n];
47         memset(C, 0, sizeof(C));
48         for (int i = 1; i <= n; i++) add(i, 1);
49         for (int i = 1; i <= n; i++) scanf("%s%d", name[i], &p[i]);
50         add(k, -1);
51         for (int i = 2; i <= pos; i++) {
52             int mod = n - i + 1, inc;
53             if (p[k] > 0)
54                 inc = (p[k] - 1) % mod + 1;
55             else
56                 inc = (p[k] % mod + mod) % mod + 1;
57             int sk = sum(k), sn = sum(n);
58             if (inc <= sn - sk) {
59                 int l = k + 1, r = n + 1;
60                 while (r - l > 1) {
61                     int mid = (r + l) / 2;
62                     if (sum(mid) <= inc + sk && sum(mid - 1) < inc + sk)
63                         l = mid;
64                     else
65                         r = mid;
66                 }
67                 k = l;
68             } else {
69                 int l = 1, r = k;
70                 while (r - l > 1) {
71                     int mid = (l + r) / 2;
72                     if (sum(mid) <= inc - sn + sk &&
73                         sum(mid - 1) < inc - sn + sk)
74                         l = mid;
75                     else
76                         r = mid;
77                 }
78                 k = l;
79             }
80             add(k, -1);
81         }
82         printf("%s %d\n", name[k], num[pos]);
83     }
84 }
View Code

 

 线段树和平方分割

 POJ 3264  查询区间的极差

  线段数维护区间最大值和最小值

 1 #include <cstdio>
 2 #include <iostream>
 3 using namespace std;
 4 
 5 const int N = 5e4 + 5;
 6 int n, q, ans1, ans2;
 7 
 8 struct node {
 9     int lch, rch, mmin, mmax;
10 } tree[4 * N];
11 
12 void build(int k, int l, int r) {
13     tree[k].lch = l, tree[k].rch = r;
14     if (tree[k].lch == tree[k].rch) {
15         scanf("%d", &tree[k].mmin);
16         tree[k].mmax = tree[k].mmin;
17         return;
18     }
19     int mid = (l + r) / 2;
20     build(k * 2, l, mid);
21     build(k * 2 + 1, mid + 1, r);
22     tree[k].mmax = max(tree[2 * k].mmax, tree[2 * k + 1].mmax);
23     tree[k].mmin = min(tree[2 * k].mmin, tree[2 * k + 1].mmin);
24 }
25 
26 void ask_interval(int k, int l, int r) {
27     if (l <= tree[k].lch && tree[k].rch <= r) {
28         ans1 = max(ans1, tree[k].mmax);
29         ans2 = min(ans2, tree[k].mmin);
30         return;
31     }
32     int mid = (tree[k].lch + tree[k].rch) / 2;
33     if (l <= mid) ask_interval(2 * k, l, r);
34     if (r > mid) ask_interval(2 * k + 1, l, r);
35 }
36 
37 int main() {
38     scanf("%d%d", &n, &q);
39     build(1, 1, n);
40     int x, y;
41     for (int i = 0; i < q; i++) {
42         scanf("%d%d", &x, &y);
43         ans1 = 0, ans2 = (int)1e6 + 1;
44         ask_interval(1, x, y);
45         printf("%d\n", ans1 - ans2);
46     }
47 }
View Code

 

POJ 3668  查询区间最大频次

  线段树维护区间最大频次,最小的数的频次,最大的数的频次。

 1 #include <cstdio>
 2 #include <iostream>
 3 using namespace std;
 4 #define P pair<int, int>
 5 const int N = 1e5 + 10;
 6 
 7 int n, q, a[N];
 8 
 9 struct data {
10     int lch, rch, i, num, ln, rn;
11     //num, ln, rn, 最大频次, 最小的数的频次, 最大的数的频次
12 } tree[4 * N], ans;
13 
14 bool operator>(const data &x1, const data &x2) { return x1.num > x2.num; }
15 
16 data comb(data x1, data x2) {
17     data res;
18     if (a[x1.rch] == a[x2.lch]) {
19         res.i = a[x1.rch];
20         res.num = x1.rn + x2.ln;
21         res = res > x1 ? res : x1;
22         res = res > x2 ? res : x2;
23     } else {
24         res = x1 > x2 ? x1 : x2;
25     }
26     res.lch = x1.lch;
27     res.rch = x2.rch;
28     if (a[x1.lch] == a[x2.lch])
29         res.ln = x1.ln + x2.ln;
30     else
31         res.ln = x1.ln;
32     if (a[x1.rch] == a[x2.rch])
33         res.rn = x1.rn + x2.rn;
34     else
35         res.rn = x2.rn;
36     return res;
37 }
38 
39 void build(int k, int l, int r) {
40     if (l == r) {
41         tree[k] = {l, r, a[l], 1, 1, 1};
42         return;
43     }
44     int mid = (l + r) / 2;
45     build(k * 2, l, mid);
46     build(k * 2 + 1, mid + 1, r);
47     tree[k] = comb(tree[k * 2], tree[k * 2 + 1]);
48 }
49 
50 void query(int k, int l, int r) {
51     if (l <= tree[k].lch && tree[k].rch <= r) {
52         ans = comb(ans, tree[k]);
53         return;
54     }
55     int mid = (tree[k].lch + tree[k].rch) / 2;
56     if (l <= mid) query(k * 2, l, r);
57     if (r > mid) query(k * 2 + 1, l, r);
58 }
59 
60 int main() {
61     while (scanf("%d", &n) != EOF, n) {
62         scanf("%d", &q);
63         for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
64         build(1, 1, n);
65         int x, y;
66         for (int i = 0; i < q; i++) {
67             scanf("%d%d", &x, &y);
68             ans = {x, x, a[x], 1, 1, 1};
69             query(1, x + 1, y);
70             printf("%d\n", ans.num);
71         }
72     }
73 }
View Code

 

 POJ 3470  给出n堵墙和m只小鸟的坐标,每只小鸟会向最先与墙发生碰撞的方向飞行(保证存在且唯一)。问每堵墙被多少只小鸟撞。

  解决方案其实就是比较明显的扫描线+线段树……但是代码量会因人而异。我自己写的就非常长,速度也慢,就不贴代码了。网上有一个七八十行的版本写的非常好……值得学习QAQ

 

 POJ 1210  给出n个区间[ai, bi](bi≤5e4)并各对应数ci, bi-ai≥ci-1。问最小的数集Z,与每一个区间重复的点数都不小于ci。

  对所有区间按左端点排序,贪心地往后放节点。线段树维护区间和(每一个节点是布尔型),并查集维护点的分布情况。数集最大是取遍1-5e4,复杂度O(nlogn)。(这个版本的代码也不是很简洁……)

 1 #include <algorithm>
 2 #include <cstdio>
 3 using namespace std;
 4 const int N = 5e4 + 10;
 5 
 6 int n;
 7 
 8 struct node {
 9     int lch, rch, w, f;
10     //累加前区间和一定小于区间长, 从而不会超出int
11 } tree[N * 4];
12 
13 void build(int k, int l, int r) {
14     tree[k] = {l, r, 0, 0};
15     if (l == r) return;
16     int mid = (l + r) / 2;
17     build(k * 2 + 1, l, mid);
18     build(k * 2 + 2, mid + 1, r);
19 }
20 
21 void down(int k) {
22     tree[k * 2 + 1].f = tree[k * 2 + 2].f = 1;
23     tree[k * 2 + 1].w = tree[k * 2 + 1].rch - tree[k * 2 + 1].lch + 1;
24     tree[k * 2 + 2].w = tree[k * 2 + 2].rch - tree[k * 2 + 2].lch + 1;
25     tree[k].f = 0;
26 }
27 
28 void add(int k, int l, int r) {
29     if (tree[k].f) return;
30     if (l <= tree[k].lch && tree[k].rch <= r) {
31         tree[k].w = tree[k].rch - tree[k].lch + 1;
32         tree[k].f = 1;
33         return;
34     }
35     if (l <= tree[k].rch && tree[k].lch <= r) {
36         int mid = (tree[k].lch + tree[k].rch) / 2;
37         if (l <= mid) add(k * 2 + 1, l, r);
38         if (r > mid) add(k * 2 + 2, l, r);
39         tree[k].w = tree[k * 2 + 1].w + tree[k * 2 + 2].w;
40     }
41 }
42 
43 int ask(int k, int l, int r) {
44     if (l <= tree[k].lch && tree[k].rch <= r) {
45         return tree[k].w;
46     }
47     if (tree[k].f) down(k);
48     if (l <= tree[k].rch && tree[k].lch <= r) {
49         int res = 0, mid = (tree[k].lch + tree[k].rch) / 2;
50         if (l <= mid) res += ask(k * 2 + 1, l, r);
51         if (r > mid) res += ask(k * 2 + 2, l, r);
52         return res;
53     }
54     return 0;
55 }
56 
57 struct data {
58     int l, r, c;
59 } a[N];
60 
61 bool cmp(data x, data y) { return x.r < y.r; }
62 
63 int par[N];
64 
65 int find(int x) {
66     if (x < 0) return -1;
67     if (x == par[x]) return x;
68     return par[x] = find(par[x]);
69 }
70 
71 int main() {
72     build(0, 0, 50000);
73     for (int i = 0; i <= 50000; i++) par[i] = i;
74     scanf("%d", &n);
75     for (int i = 0; i < n; i++) scanf("%d%d%d", &a[i].l, &a[i].r, &a[i].c);
76     sort(a, a + n, cmp);
77     for (int i = 0; i < n; i++) {
78         int tot = ask(0, a[i].l, a[i].r);
79         int j;
80         for (j = find(a[i].r); tot < a[i].c && a[i].r - j < a[i].c;
81              j = find(j - 1)) {
82             tot++;
83             par[j]--;
84         }
85         add(0, j + 1, a[i].r);
86     }
87     printf("%d", ask(0, 0, 50000));
88 }
View Code

 

UVA 11990  给出一个n(≤2e5)的排列,有m(≤1e5)次删除操作,输出每次输出前数列的逆序对数。

  对于在位置i的数xi,设想在二维坐标上的点(i, x),相当于查询删除的点左上方和右下方有多少个点。对横坐标和纵坐标分块,每个块储存所在行的块的前缀和。

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 #define ll long long
 4 const int N = 2e5 + 5;
 5 const int SN = 450;
 6 
 7 int n, m;
 8 ll res;
 9 int X[N], Y[N];
10 int sum[SN][SN];
11 
12 void add(int x, int y) {
13     X[x] = y;
14     Y[y] = x;
15     for (int i = x / SN; i < n / SN; i++) sum[i][y / SN]++;
16 }
17 
18 void remove(int x, int y) {
19     X[x] = Y[y] = -1;
20     for (int i = x / SN; i < n / SN; i++) sum[i][y / SN]--;
21 }
22 
23 int Sum(int x, int y) {  //包含点(x,y)
24     int ans = 0, bx = x / SN, by = y / SN;
25     if (bx > 0) {
26         for (int i = 0; i < by; i++) ans += sum[bx - 1][i];
27     }
28     for (int i = bx * SN; i <= x; i++) {
29         if (X[i] != -1 && X[i] <= y) ans++;
30     }
31     for (int i = by * SN; i <= y; i++) {
32         if (Y[i] != -1 && Y[i] < bx * SN) ans++;
33     }
34     return ans;
35 }
36 
37 int inv(int x, int y) { return Sum(x, n - 1) + Sum(n - 1, y) - 2 * Sum(x, y); }
38 
39 int main() {
40     while (scanf("%d%d", &n, &m) != EOF) {
41         memset(X, -1, sizeof(X));
42         memset(Y, -1, sizeof(Y));
43         memset(sum, 0, sizeof(sum));
44         res = 0;
45         int x;
46         for (int i = 0; i < n; i++) {
47             scanf("%d", &x);
48             x--;
49             res += inv(x, i);
50             add(x, i);
51         }
52         for (int i = 0; i < m; i++) {
53             scanf("%d", &x);
54             x--;
55             printf("%lld\n", res);
56             int y = X[x];
57             remove(x, y);
58             res -= inv(x, y);
59         }
60     }
61 }
View Code

 

 

END

posted @ 2020-02-11 20:49  Linqi05  阅读(2)  评论(0)    收藏  举报