您的位置:首页 > 其它

POJ2299【树状数组】

2015-10-24 13:13 225 查看
/* 转载请注明出处, 谢谢 */

目的:理 解 树 状 数 组 的 设 计 思 想 。 /* 在 写 之 前 让 我 先 膜 拜 一 下 ,orz 。 */

1 楼 祭 图:






树状 数 组 , 最 经 典 的 应 用 就 是 求 区 间 和 [ l , r ] 的 和 ( 支 持 修 改 操 作 ),为了更好的理解,我们就以这个场景举例子 。

比 如 对 于 一 个 序列 { An } ,我们 用 这样 一 个 数 组 C [ n ] 维 护 :

C [ i ] = A [ i - 2^ k + 1 ] + A [ i - 2 ^ k + 2 ] + … + A [ 1 ] ; (A
从下标1开始记)。

其 中 k 表 示 i 转 换 成二 进 制 末 尾 低 位 上 连 续 0 的 个 数 。比 如 i = 6, 即 1 1 0 (这个数据结构太6,我 要 报 警 啦) , k
= 1


然 后 我 们 以 { An } , n == 8 时 为 例 ,写 出 所 有 的 C


C[1] = A[1] ;

C[2] = A[1] + A[2];

C[3] = A[3];

C[4] = A[1] + A[2] + A[3] + A[4];

C[5] = A[5];

C[6] = A[5] + A[6];

C[7] = A[7];

C[8] = A[1] + A[2] + A[3] + A[4] + A[5] + A[6] + A[7] + A[8];

然 后 , 根 据 C [ i ] 的 定 义 定 义 ,可 知 道 每 个 C [ i ] 所 “ 管 辖 ” 的 范 围 就 是,是 啥 呢 ? 这 得 换 成 二 进 制 说 ,假 设 是 i 换 成 二 进 制 是110100,110100 = 110000 + 100,
后 所 管 辖 的 范 围 就 是 110001 ~ 110100,这 有 什 么 好 处 呢 ? 其 实 这 就 类 似 与 快 速 幂 的 思 想 ,同 样 是 利 用 了 二 进 制

的 性 质

从求和过程观察其原理。下 面 我 们 举 个 栗 子:

我 们 要 算 区 间 [ l , r ] 的 和 , 那 么 我 们 首 先 转 化 问 题 :ans = sum[ r ] - sum[ l ]; 其 中 sum[ i ] , 是 指 区 间 [ 1 , i ] 的 和。

那么我们只要考虑 sum[ i ] 怎么算就阔以了。

/*******************************************************************************************************************************************************************************

以 i = 10 举 例 解 释 吧:

1. sum = 0 初 始 化.

2. 我 们 先 转 化 成 二 进 制 i = 1010,k = 1. C[ i ] 管 辖 的 是 {An} 中 1001 - 1010 的 范 围 . sum += C[ i ];

3. 然 后 我 们 把 i 管 线 的 范 围 剔 除 ,由 上 面 定 义 和 观 察,我 们 已 经 知 道 1010 管 辖 的 是 1001 - 1010 总 共 2 ^ k 大 的 范 围 ,而且他 的 上 限 是 i,

下 线 是 i - 2 ^ k + 1。

4. 那 我 们 删 去 2^k 后 就 获 得 紧 跟 i 先 下 限 的 的 管 线 范 围 , 即 i -= 2 ^ k , i = 1000, 这 次 i 管 辖 的 就 是 0001 - 1000 了, sum += C[i];

5. 重 复 4 操 作 ,直 到 i == 0, 我 们 获 得 的 就 是[ 1 ,i ] 所 有 的 数的 和。

看 到 没 !! 每 次 操 作 最 少 就 去 掉 一 个 1 !! 。 一 个 数 N ,转 化 成 二 进 制 最 多 有 LogN 个 1 吧 !所 以 每 次 求 和 操 作 的 复 杂 度 是 O(LogN)!!

*********************************************************************************************************************************************************************************/

是 不 是 跟 快 速 幂 有 异 曲 同 工 之 妙 ? !

请仔细思考上述求和操作,然后我们大概说一下点修改操作,类似于上面的操作。

大 概 就 是 对 于 A[ i ] , C [ i + 2 ^k ] 以 及 C[ ( i + 2 ^k ) + 2 ^ k' ] ,( 这 里 是 个 迭 代 过 程 )都 包 含 A [ i ] ; 算了,不说了。上代码了。

POJ 2299:

求 长 度 为 N 的 乱 序 数 列 的 逆 序 数,N <= 500000.

问 题 转 化 为 求 每 个 数 之 前 的 比 它 大 的 数 的 个 数 的 和 。

记 录 每 个 数 的 位 置 ,按 大 小 排 序 ,然 后 按 照 大 小 插 入 他 们 原 本 的 位 置 当 中 , 即 每 次 更 新 操 作 , 令 a[ val[i].p ] = 1 。

接 着 转 化 ,sum[i] 为 i 之 前 比 a[i] 小 的 数 的 个 数(因 为 按 从 小 到 大 插 入,所 以 比 a[i] 小 的 数 已 经 全 部都 插 入 位 置 了),i - 1为 当 前 已 插 入 自 己 位 置 的 个 数。

i - sum[i] - 1 就 是 在 i 之 前 的 比 a[i ] 大 的 个 数。

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int size = 500010;
int N;
struct Val
{
int p, v;
}val[size];
int C[size];
int cmp(const Val &a, const Val &b)
{
return a.v < b.v;
}
inline int lowBit(int x)
{
return x & ( -x );
}
inline int sum(int x)
{
int ret = 0;
while(x > 0)
{
ret += C[x];
x -= lowBit(x);
}
return ret;
}
inline void update(int x)
{
while(x <= N)
{
C[x] += 1;
x += lowBit(x);
}
}
int main(int argc, char const *argv[])
{
while(scanf("%d", &N) && N)
{
long long ans = 0, l , r;

memset(C,0,sizeof(C));
for(int i = 1; i <= N; i ++)
{
scanf("%d",&val[i].v);
val[i].p = i;
}
sort(val + 1, val + N + 1,cmp);
for(int i = 1; i <= N; i ++)
{
l = sum(val[i].p);
r = i - l - 1;
ans += r;
update(val[i].p);
}
printf("%lld\n",ans);
}
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: