赞
踩
有一个序列w,初始为空。再给出一个长度为m单调递增的序列a。你需要对序列w作如下n次操作:
(1)操作0,在序列尾部添加数字0。
(2)操作1,在序列尾部添加数字1.
(3)操作-1,删除序列w中,所有位于位置ai的数(1<=i<=m)。比如 a={1,3,5},就将w中第1,3,5个数删除。若ai>w 的当前长度,则该操作停止。
输出n次操作后的序列w。
树状数组、二分查找
本题最大的两个特征为数据范围大,对数据的操作次数多。基于这两个特点,我们采用两个对应的方法,即二分查找(以便在数据范围大时寻找数据)和树状数组(以便删除数据后,数组可以快速调整)。
二分无需赘述,我们重点介绍一下树状数组。
树状数组,顾名思义,就是用数组来模拟树形结构。那么衍生出一个问题,为什么不直接建树?答案是没必要,因为树状数组能处理的问题就没必要建树。我们仅需两个数组便能存储和快速处理树状数组的各项数据。
这里采用树状数组,是因为其修改和查询的复杂度都是 O ( l o g N ) O(logN) O(logN),而且相比线段树系数要少很多,比传统数组要快,而且容易写。
下面是关于树状数组的基础概念。
二叉树大家一定都知道,如下图:
图
G
−
1
经
典
二
叉
树
的
图
示
图G-1 \ \ \ \ 经典二叉树的图示
图G−1 经典二叉树的图示
如果每个父亲都存的是两个儿子的值,这样的树形结构,叫做线段树。这里不用线段树是因为线段树虽然搜索速度快,但调整速度不如树状数组,另外,线段树解决此题显得有点大材小用。
真正的树状数组和上图类似,但省去了一些节点,以便用数组建树。
图
G
−
2
树
状
数
组
的
一
个
图
示
(
图
源
网
络
)
图G-2 \ \ \ \ 树状数组的一个图示(图源网络)
图G−2 树状数组的一个图示(图源网络)
黑色数组代表原来的数组(下面用 A [ i ] A[i] A[i]代替),红色结构代表我们的树状数组(下面用 C [ i ] C[i] C[i]代替),发现没有,每个位置只有一个方框,令每个位置存的就是子节点的值的和,则有:
可以发现,这颗树是有规律的:
C [ i ] = A [ i − 2 k + 1 ] + A [ i − 2 k + 2 ] + . . . + A [ i ] C[i] = A[i - 2^k+1] + A[i - 2^k+2] + ... + A[i] C[i]=A[i−2k+1]+A[i−2k+2]+...+A[i],其中 k k k为 i i i的二进制中从最低位到高位连续零的长度。
例如 i = 8 ( 1000 ) i = 8(1000) i=8(1000)时候, k = 3 k = 3 k=3,可自行验证。
这个怎么实现求和呢,比如我们要找前 7 7 7项和,那么应该是 S U M = C [ 7 ] + C [ 6 ] + C [ 4 ] SUM = C[7] + C[6] + C[4] SUM=C[7]+C[6]+C[4]
而根据上面的式子,容易的出 S U M i = C [ i ] + C [ i − 2 k 1 ] + C [ ( i − 2 k 1 ) − 2 k 2 ] + . . . SUM_i = C[i] + C[i-2^{k1}] + C[(i - 2^{k1}) - 2^{k2}] + ... SUMi=C[i]+C[i−2k1]+C[(i−2k1)−2k2]+...
其实树状数组就是一个二进制上面的应用。
现在新的问题来了, 2 k 2^k 2k该怎么求呢,不难得出 2 k = i & ( i ( i − 1 ) ) 2^k = i\&(i^{(i-1)}) 2k=i&(i(i−1)),但这个还是不好求出,前辈的智慧就出来了, 2 k = i & ( − i ) 2^k = i\&(-i) 2k=i&(−i)。我们引用数论里的证明过程对此进行说明。
这里利用的负数的存储特性,负数是以补码存储的,对于整数运算 x & ( − x ) x\&(-x) x&(−x)有
- 当 x x x为 0 0 0时,即 0 & 0 0 \& 0 0&0,结果为 0 0 0;
- 当 x x x为奇数时,最后一个比特位为 1 1 1,取反加 1 1 1没有进位,故 x x x和 − x -x −x除最后一位外前面的位正好相反,按位与结果为 0 0 0。结果为 1 1 1。
- 当 x x x为偶数,且为 2 2 2的 m m m次方时, x x x的二进制表示中只有一位是 1 1 1(从右往左的第 m + 1 m+1 m+1位),其右边有 m m m位 0 0 0,故 x x x取反加 1 1 1后,从右到左第有 m m m个 0 0 0,第 m + 1 m+1 m+1位及其左边全是 1 1 1。这样, x & ( − x ) x\& (-x) x&(−x) 得到的就是x。
- 当 x x x为偶数,却不为 2 2 2的 m m m次方的形式时,可以写作 x = y ∗ ( 2 k ) x = y * (2^k) x=y∗(2k)。其中, y y y的最低位为 1 1 1。实际上就是把x用一个奇数左移 k k k位来表示。这时, x x x的二进制表示最右边有 k k k个 0 0 0,从右往左第 k + 1 k+1 k+1位为 1 1 1。当对 x x x取反时,最右边的 k k k位 0 0 0变成 1 1 1,第 k + 1 k+1 k+1位变为 0 0 0;再加 1 1 1,最右边的 k k k位就又变成了 0 0 0,第 k + 1 k+1 k+1位因为进位的关系变成了 1 1 1。左边的位因为没有进位,正好和 x x x原来对应的位上的值相反。二者按位与,得到:第 k + 1 k+1 k+1位上为 1 1 1,左边右边都为 0 0 0。结果为 2 k 2^k 2k。
- 总结一下: x & ( − x ) x\&(-x) x&(−x),当 x x x为 0 0 0时结果为 0 0 0; x x x为奇数时,结果为 1 1 1; x x x为偶数时,结果为 x x x中 2 2 2的最大次方的因子。
这个有一个专门的称呼,叫做 l o w b i t lowbit lowbit,即取 2 k 2^k 2k。
基于上述描述,我们给出树状数组的构造程序(非原创):
int n; int a[1005],c[1005]; //对应原数组和树状数组 int lowbit(int x){ return x&(-x); } void add(int i,int k){ //在i位置加上k while(i <= n){ c[i] += k; i += lowbit(i); } } int getsum(int i){ //求A[1 - i]的和 int res = 0; while(i > 0){ res += c[i]; i -= lowbit(i); } return res; }
有了树状数组和二分查找,本题的思路就显得十分清晰。本题有三个操作,其中一二两个操作本质上毫无区别,我们给出对应的树状数组的处理策略:
完整的操作详见程序。
#include<bits/stdc++.h> using namespace std; namespace fast_IO { inline int read() { int x = 0, f = 1; char ch = getchar(); while (!isdigit(ch)) { if (ch == '-') f = -1; ch = getchar(); } while (isdigit(ch)) x = x * 10 + (ch ^ 48), ch = getchar(); return x * f; } }; using namespace fast_IO; const int N = 1e6 + 5; int c[N], a[N], ans[N], top, as[N]; int n, m, sum; int lowbit(int x) { return x & (-x); } void add(int x, int val) { while (x <= m) { c[x] += val; x += lowbit(x); } } int getsum(int x) { int ans = 0; while (x) { ans += c[x]; x -= lowbit(x); } return ans; } void solve(int num, int ct) { int l = 1, r = m; while (l < r) { int mid = (l + r) / 2; if (getsum(mid) >= num) r = mid; else l = mid + 1; } as[ct] = l; } int t; int main() { cin >> m >> n; memset(ans, -1, sizeof(ans)); for (int i = 1; i <= n; i++) { a[i] = read(); } for (int i = 1; i <= m; i++) { t = read(); if (t == 0 || t == 1) { ans[++top] = t; sum++; add(top, 1); } else { int temp = lower_bound(a + 1, a + n + 1, sum) - a; sum -= temp > n || a[temp] > sum ? --temp : temp; for (int i = 1; i <= temp; i++) solve(a[i], i); for (int i = 1; i <= temp; i++) { add(as[i], -1); ans[as[i]] = -1; } } } bool flag = false; for (int i = 1; i <= m; i++) { if (ans[i] == -1) continue; cout << ans[i]; flag = true; } if (!flag) cout << "Poor stack!"; return 0; }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。