挑战程序设计竞赛 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 }
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 }
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 }
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 }
线段树和平方分割
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 }
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 }
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 }
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 }
END


浙公网安备 33010602011771号