题意是不同颜色区间首尾相接,询问一个区间内同色区间的最长长度。
网上流行的做法,包括翻出来之前POJ的代码也是RMQ做法,对于序列上的每个数,记录该数向左和向右延续的最远位置,那么对于一个查询Q(L, R),它的答案就分成了三种情况right(L) - L,R - left(R)以及Q(L+right(L),R-left(R))。
这里给出一个线段树做法,在线段树的节点上维护3个量:l_value, r_value, value分别表示以左端点为起始点,以右端点为起始点以及该区间内的最大的连续长度,更新时通过两个子区间相接的地方是否相同分不同的情况进行讨论。
#include <cstdio> #include <algorithm> using namespace std; const int MAXN = 100005; class SegNode { public: int L, R; int l_value, r_value, value; int is_same; } node[4 * MAXN]; int num[MAXN]; class SegTree { public: void log(int idx) { printf("%d: ", idx); for (int i = node[idx].L; i <= node[idx].R; i++) printf("%d ", num[i]); printf("(%d %d %d %d)\n", node[idx].l_value, node[idx].r_value, node[idx].value, node[idx].is_same); } void build(int root, int L, int R) { node[root].L = L; node[root].R = R; if (L == R) { // leaf node[root].l_value = 1; node[root].r_value = 1; node[root].value = 1; node[root].is_same = 1; } else { // non leaf int M = (L + R) / 2; if (L <= M) { build(2 * root, L, M); } if (M + 1 <= R) { build(2 * root + 1, M + 1, R); } if (num[node[2 * root].R] == num[node[2 * root + 1].L]) { node[root].l_value = node[2 * root].l_value + node[2 * root].is_same * node[2 * root + 1].l_value; node[root].r_value = node[2 * root + 1].r_value + node[2 * root + 1].is_same * node[2 * root].r_value; node[root].value = max(max(node[2 * root].value, node[2 * root + 1].value), node[2 * root].r_value + node[2 * root + 1].l_value); node[root].is_same = node[2 * root].is_same & node[2 * root + 1].is_same; } else { node[root].l_value = node[2 * root].l_value; node[root].r_value = node[2 * root + 1].r_value; node[root].value = max(node[2 * root].value, node[2 * root + 1].value); node[root].is_same = 0; } //log(root); } } int query(int root, int L, int R, int k) { if (L <= node[root].L && R >= node[root].R) { if (k == 0) return node[root].value; else if (k == 1) return node[root].l_value; else return node[root].r_value; } if (L > node[root].R || R < node[root].L) { return 0; } int M = (node[root].L + node[root].R) / 2; if (R <= M) { return query(2 * root, L, R, k); } else if (L > M) { return query(2 * root + 1, L, R, k); } else { if (num[node[2 * root].R] == num[node[2 * root + 1].L]) { if (k == 0) { int res = 0; res = max(query(2 * root, L, R, 0), query(2 * root + 1, L, R, 0)); res = max(res, query(2 * root, L, R, 2) + query(2 * root + 1, L, R, 1)); return res; } else if (k == 1) { int res = query(2 * root, L, R, 1); if (node[2 * root].is_same) res += query(2 * root + 1, L, R, 1); return res; } else { int res = query(2 * root + 1, L, R, 2); if (node[2 * root + 1].is_same) res += query(2 * root, L, R, 2); return res; } } else { if (k == 0) { return max(query(2 * root, L, R, 0), query(2 * root + 1, L, R, 0)); } else if (k == 1) { return query(2 * root, L, R, 1); } else { return query(2 * root + 1, L, R, 2); } } } } } tree; int main() { int n, q; while (scanf("%d%d", &n, &q) && n) { for (int i = 1; i <= n; i++) scanf("%d", &num[i]); tree.build(1, 1, n); while (q--) { int l, r; scanf("%d%d", &l, &r); printf("%d\n", tree.query(1, l, r, 0)); } } }
由这题想到了最大连续子段和这个问题,常见的解法是动态规划解法,算法课上讲了一个分治的解法,将整段分成左右两半,然后在中间点处向左和向右遍历,寻找最长的连续段,算法复杂度分析T(n)=2T(n/2)+O(n),因此复杂度是O(nlgn)。然而可以使用上面的思路进行维护,维护一个以左端点为起点的最长连续子段,该子段记作left(root),讲root分成L,R,那么left(root)=max{left(L), sum(L)+left(R)},这样查询中点mid的最优值就可以用right(L)+left(R)来替代了,复杂度为O(1),最后也就可以做到复杂度为O(nlgn)的最大子段和的分治算法了。
题意是各个序列,统计这样的三元组(a,b,c),满足条件idx(a)<idx(b)<idx(c),且a<b<c或者a>b>c的数量。
做法是用树状数组统计出给定一个索引i,i左侧比a[i]小的数量,以及右侧比a[i]小的数量,用左侧比a[i]小的数量乘上右侧比a[i]大的数量,以及左侧比a[i]大的数量乘上右侧比a[i]小的数量。
#include <cstdio> #include <cstring> using namespace std; const int MAXA = 100005; const int MAXN = 20005; int c[MAXA]; int a[MAXN]; int left[MAXN], right[MAXN]; int lowbit(int x) { return x & (-x); } void insert(int i, int x) { while (i < MAXA) { c[i] += x; i += lowbit(i); } } int query(int i) { int res = 0; while (i > 0) { res += c[i]; i -= lowbit(i); } return res; } int main() { int T; scanf("%d", &T); while (T--) { int n; scanf("%d", &n); for (int i = 0; i < n; i++) scanf("%d", &a[i]); memset(c, 0, sizeof(c)); for (int i = 0; i < n; i++) { left[i] = query(a[i]); insert(a[i], 1); } memset(c, 0, sizeof(c)); for (int i = n - 1; i >= 0; i--) { right[i] = query(a[i]); insert(a[i], 1); } long long ans = 0; for (int i = 0; i < n; i++) { ans += (long long)left[i] * (n - 1 - i - right[i]); ans += (i - left[i]) * (long long)right[i]; } printf("%lld\n", ans); } }