《算法笔记》 第十三章 专题扩展

分块思想

序列元素第K大问题

const int maxn=100010;
const int sqrtN=316;//根号100010

int block[sqrN];//block[i]记录了i块存放元素个数 
int table[maxn];//table[i]元素i的个数 

void peekMedian(int K)
{
	int sum=0;
	int idx=0;
	while(sum+block[idx]<K)
	{
		sum+=block[idx++];
	} 
	int num=idx*sqrN;
	while(sum+table[num]<K)
	{
		sum+=table[num++];
	}
	printf("%d",num);
} 

树状数组BIT

lowbit函数
lowbit(x)代表能整除x的最大二次幂

int lowbit(int x)
{
	return x & -x;
}

给出一个整数序列A,元素个数为N,接下来的K次对整个数组进行查询或插入操作,每次给出一个查询的数x,要求能实时查询前x个数的和。

const int maxv=10001;
int N=0;
int A[maxv]={0};
int C[maxv]={0};

int lowbit(int x)
{
	return x & -x;
}

//构造C数组 
int creatc()
{
	for(int i=0;i<N;i++)
	{
		for(int j=i-lowbit(i)+1;j<=i;j++)
		{
			C[i]+=A[j];
		}
	}
}

//getSum函数返回前x个整数之和
int grtSum(int x)
{
	int sum=0;
	for(int i=x;i>0;i-=lowbit(i))
	{
		sum+=c[i];
	}
	return sum;
} 

//update函数将第x个整数加上v
void update(int x,int v)
{
	for(int i=x;i<=N;i++)
	{
		C[i]+=v;
	}
} 

若序列为二维的,则将代码修改为:

//getSum函数返回前x个整数之和
int getSum(int x,int y)
{
	int sum=0;
	for(int i=x;i>0;i-=lowbit(i))
	{
		for(int j=y;j>0;j-=lowbit(j))
		{
			sum+=C[i][j];
		}	
	}
	return sum;
} 

//update函数将第x个整数加上v
void update(int x,int y,int v)
{
	for(int i=x;i<=N;i++)
	{
		for(int j=y;j<maxn;j++)
		{
			C[i][j]+=v;
		}
	}
} 

如果想求A[a][b]~A[x][y]这个子矩阵元素的和,只需要计算getSum(x,y)-getSum(x-1,y)-getSum(x,y-1)+getSum(x-1,y-1)
如果是区间更新,就是将A[1]到A[x]上的每一个数字都加v,则将getSum和update函数换成如下形式

//getSum函数返回前x个整数之和
int getSum(int x)
{
	int sum=0;
	for(int i=x;i>0;i-=lowbit(i))
	{
		sum+=C[i];	
	}
	return sum;
} 

//update函数将第x个整数加上v
void update(int x,int v)
{
	for(int i=x;i<=N;i++)
	{
		C[i][j]+=v;
	}
}

如果是要让A[x]~A[y]的每个数加上v,则执行
update(y,v);
update(x-1,-v);
经典例题:
给定一个有N个正整数的序列A,对序列中的每个数,求出序列中他左边比它小的数的个数。

#include <iostream>
#include <cstdio> 
using namespace std;
const int maxv=10001;
int N=0;
int A[maxv]={0};
int C[maxv]={0};

int lowbit(int x)
{
	return x & -x;
}

//getSum函数返回前x个整数之和
int getSum(int x)
{
	int sum=0;
	for(int i=x;i>0;i-=lowbit(i))
	{
		sum+=C[i];
	}
	return sum;
} 

//update函数将第x个整数加上v
void update(int x,int v)
{
	for(int i=x;i<=N;i++)
	{
		C[i]+=v;
	}
} 

int main() {
	scanf("%d",&N);
	int x;
	for(int i=0;i<N;i++)
	{
		scanf("%d",&x);
		update(x,1);
		printf("%d\n",getSum(x-1));
	}
	return 0;
}

用树状数组代替了哈希表,简化查找过程
若需要统计元素左边比它大的数的个数,只需要将最后输出语句改为

printf("%d\n",getSum(N)-getSum(A[i]));//这里的A[i]就是x

离散化思想
若数组中的元素值过大,可以用其他较小的值来替换他,只用表示出元素与元素之间的大小关系即可,例如[520,99999,18,666,8888]等价于[2,5,1,3,4]

#include <iostream>
#include <cstdio> 
using namespace std;
const int maxv=10001;
int N=0;
int A[maxv]={0};
int C[maxv]={0};

struct Node{
	int val;
	int pos;
}temp[maxn];

int lowbit(int x)
{
	return x & -x;
}

//getSum函数返回前x个整数之和
int getSum(int x)
{
	int sum=0;
	for(int i=x;i>0;i-=lowbit(i))
	{
		sum+=C[i];
	}
	return sum;
} 

//update函数将第x个整数加上v
void update(int x,int v)
{
	for(int i=x;i<=N;i++)
	{
		C[i]+=v;
	}
} 

bool cmp(Node a,Node b)
{
	return a.val<b.val;
}

int main() {
	scanf("%d",&N);
	int x;
	for(int i=0;i<N;i++)
	{
		scanf("%d",&temp[i].val);
		temp[i].pos=i;
	}
	sort(temp,temp+n,cmp);
	for(int i=0;i<n;i++)
	{
		if(i==0||temp[i].val!=temp[i-1].val)
		{
			A[temp[i].pos]=i+1;
		}
		else
		{
			A[temp[i].pos]=A[temp[i-1].pos];
		}
	}
	for(int i=0;i<n;i++)
	{
		update(A[i],1);
		printf("%d\n",getSum(A[i]-1));
	}
	return 0;
}

利用树状数组求解第K大问题

//getSum函数返回前x个整数之和
int getSum(int x)
{
	int sum=0;
	for(int i=x;i>0;i-=lowbit(i))
	{
		sum+=C[i];
	}
	return sum;
}

//求元素序列第K大问题(这里假设序列长度[1,maxn])
int findKthElement(int K)
{
	int l=1,r=maxn,mid;
	while(l<r)
	{
		mid=(l+r)/2;
		if(getSum(mid)>=K)
		{
			r=mid;
		}
	} 
	return l;
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值