SPOJ TEMPLEQ - Temple Queues(二分查找+树状数组)
题意:
有N个队伍(1 <= N <= 100,000),每个队伍开始有ai个人[0 <= ai<= 100,000,000],有Q个操作[0<=Q<= 500,000]
操作分为三种,1 A:表示在第A个队列加一个人。 2 X:表示求长度大于等于X队列数量。3 Y:表示所有长度大于等于Y的队列减去一个人。
题解:
把各个队列按长度排序
用差分数列来维护这个数组,这样求每个队列的长度就是求前缀和。每次求长度的复杂度是lgn,因为队列是按长度排序的,所以可以通过二分查找到某个长度在队列中的位置,复杂度为lgn*lgn。
两个数组sa[i]记录每个按长度排序后的第i个队列原来的位置。 rk[i]记录在位置i的队列按长度排序的位置。
对于在第i个队列加一个人,求出第i个队列的长度len,在所有长度为len的队列的最后一个加一,这样操作是为了不改变队列的顺序,然后是需要交换下i的位置和队列中长度为len的最后一个位置就好了。
AC代码(950MS):
#include <bits/stdc++.h> using namespace std; const int N = 100005; struct Node { int pos; int len; bool operator < (Node x) const { return len < x.len; } } a[N]; int sa[N], rk[N], bit[N]; int n, q; int lowbit(int x) { return x & -x; } void add(int pos, int val) { while (pos <= n) { bit[pos] += val; pos += lowbit(pos); } } int sum(int pos) { int res = 0; while (pos) { res += bit[pos]; pos -= lowbit(pos); } return res; } int lb(int x) { // 找第一个大于等于x的数 int l = 1, r = n + 1, m; while (l < r) { m = (l + r) >> 1; if (sum(m) < x) l = m + 1; else r = m; } return r; } int main() { while (~scanf("%d%d", &n, &q)) { for (int i = 1; i <= n; ++i) { scanf("%d", &a[i].len); a[i].pos = i; } sort(a + 1, a + 1 + n); for (int i = 1; i <= n; ++i) { sa[i] = a[i].pos; rk[ a[i].pos ] = i; add(i, a[i].len - a[i - 1].len); } int ch, x; while (q--) { scanf("%d%d", &ch, &x); if (ch == 1) { int bp = rk[x]; // 原来的位置 int len = sum(bp); int sp = lb(len + 1) - 1; // 加一后的位置 swap(rk[ sa[bp] ], rk[ sa[sp] ]); swap(sa[bp], sa[sp]); add(sp, 1); add(sp + 1, -1); } else if (ch == 2) { int ans = lb(x); printf("%d\n", n - ans + 1); } else { int sp = lb(x); add(sp, -1); } } } return 0; }