POJ2299【树状数组】

/* 转载请注明出处, 谢谢 */


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


1 楼 祭 图:



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


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


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


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



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

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;
}




         

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值