树状数组POJ3067

#include<iostream>
#include<algorithm>
#define M 2005
using namespace std;

long long tree[M];//tree[]里面的为部分和,也会达到long long级别
long long ans;//否则runtime error 
//有重边的情况下交点最多可能达到O((k/2)^2)级别
struct edge
{
	int left,right;
}road[M*M];

bool cmp(edge a,edge b)
{
	if(a.left==b.left)
		return a.right<b.right;
	else
		return a.left<b.left;
}

int lowbit(int t)//计算t的最右边的非零位,如6(00110)返回2(00010)
{
	return t&(-t);
}

void insert(int k,int val,int maxVal)//对位置k上插入一个val
{
	while(k<=maxVal)
	{
		tree[k]=tree[k]+val;
		k=k+lowbit(k);
	}
}

long long sum(int k)//求下标k处的部分和
{
	long long sum=0;
	while(k>0)
	{
		sum=sum+tree[k];
		k=k-lowbit(k);
	}
	return sum;
}

int main()
{
	int T;
	//cin>>T;//超时!!!!!!!!!!!!!!!
	scanf("%d",&T);
	for(int tt=1;tt<=T;tt++)
	{
		int  n,m,k;
		int maxVal=0;
		//cin>>n>>m>>k;
		scanf("%d%d%d",&n,&m,&k);
		for(int i=1;i<=k;i++)
		{
			scanf("%d%d",&road[i].left,&road[i].right);
			//cin>>road[i].left>>road[i].right;
			if(road[i].right>maxVal)
				maxVal=road[i].right;
		}

		sort(road+1,road+k+1,cmp);		

		memset(tree, 0, sizeof(tree));//!!!!!!!!
		ans=0;

		for(int i=1;i<=k;i++)
		{
			ans=ans+(sum(maxVal)-sum(road[i].right));//插入时,当前总节点数为sum(maxVal),因为每次插入时都会向上更新到tree[maxVal]
			insert(road[i].right,1,maxVal);
		}		
		//cout<<"Test case "<<tt<<": "<<ans<<endl;
		printf("Test case %d: %I64d\n",tt,ans);
	}
	return 0;
}


树状数组,又称二进制索引树,英文名Binary Indexed Tree。

一、树状数组的用途

主要用来求解数列的前缀和,a[0]+a[1]+...+a[n]。

由此引申出三类比较常见问题:

1、单点更新,区间求值。(HDU1166)

2、区间更新,单点求值。(HDU1556)

3、求逆序对。(HDU2838)

 

 假如我现在有个需求,就是要频繁的求数组的前n项和,并且存在着数组中某些数字的频繁修改,那么我们该如何实现这样的需求?

① 传统方法:根据索引修改为O(1),但是求前n项和为O(n)。

②空间换时间方法:我开一个数组sum[],sum[i]=a[1]+....+a[i],那么有点意思,求n项和为O(1),但是修改却成了O(N),这是因为我的Sum[i]中牵涉的数据太多了,那么问题来了,我能不能在相应的sum[i]中只保存某些a[i]的值呢?好吧,下面我们看张图。

从图中我们可以看到S[]的分布变成了一颗树,有意思吧,下面我们看看S[i]中到底存放着哪些a[i]的值。

S[1]=a[1];

S[2]=a[1]+a[2];

S[3]=a[3];

S[4]=a[1]+a[2]+a[3]+a[4];

S[5]=a[5];

S[6]=a[5]+a[6];

S[7]=a[7];

S[8]=a[1]+a[2]+a[3]+a[4]+a[5]+a[6]+a[7]+a[8];

之所以采用这样的分布方式,是因为我们使用的是这样的一个公式:S[i]=a[i-2k+1]+....+a[i]

其中:2k 中的k表示当前S[i]在树中的层数,它的值就是i的二进制中末尾连续0的个数,也是(最低位1的位置,最低位序号为0的话)2k也就是表示S[i]中包含了几个a[]

举个例子:  i=610=0110;可以发现末尾连续的0有一个,即k=1,则说明S[6]是在树中的第二层,并且S[6]中有21项,(同理,8->0100的最低位1的位置为2,S[8]在第四层,S[8]中为4个S[]的和)随后我们求出了起始项:

            a[6-21+1]=a[5],但是在编码中求出k的值还是有点麻烦的,所以我们采用更灵巧的Lowbit技术,即:2k=i&-i

           则:S[6]=a[6-21+1]=a[6-(6&-6)+1]=a[5]+a[6]。

2:求前n项和

     比如上图中,如何求Sum(6),很显然Sum(6)=S4+S6,那么如何寻找S4呢?即找到6以前的所有最大子树,很显然这个求和的复杂度为logN

3:修改

如上图中,如果我修改了a[5]的值,那么包含a[5]的S[5],S[6],S[8]的区间值都需要同步修改,我们看到只要沿着S[5]一直回溯到根即可,

同样修改的时间复杂度也为logN

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值