《算法竞赛进阶指南》0x06倍增

倍增算法

例题
acwing109.天才ACM

1.对于本题,如果我们采用二分的方法,最坏时间复杂度是 O ( n 2 l o g n ) O(n^2logn) O(n2logn)
证明:最坏的情况是每次往后走一个位置,一共需要走n次,每次需要花费的时间是:
n / 2 l o g ( n / 2 ) + n / 4 l o g ( n / 4 ) + . . . + 1 l o g 1 ≤ n l o g n n/2log(n/2)+n/4log(n/4)+...+1log1\leq nlogn n/2log(n/2)+n/4log(n/4)+...+1log1nlogn
因此时间复杂复杂度为 n ∗ n l o g n = n 2 l o g n n*nlogn=n^2logn nnlogn=n2logn
2.如果采用倍增的方法,可以这么来写:

  1. 初始化p=1,R=L
  2. 求出[L,R+p]这一段区间的校验值,如果校验值 ≤ T \leq T T,则R+=p,p*=2否则p/=2
  3. 重复上面每一步,直到p=0,R为所求当前段的最右位置。

对于普通的倍增时间复杂度为 O ( n l o g 2 n ) O(nlog^2n) O(nlog2n)
证明:对于找到一段符合条件的len1(长度逐渐变长直到len1),最多需要倍增log(n)次,这样对于一段的排序总共需要<log(n)len1log(len1)的时间(因为在len1之前的长度都比他短)。总共的时间为 l o g ( n ) ( l e n 1 l o g ( l e n 1 ) + … + l e n k l o g ( l e n k ) ) < l o g ( n ) n l o g ( n ) log(n)(len1log(len1)+…+lenklog(lenk))<log(n)nlog(n) log(n)(len1log(len1)++lenklog(lenk))<log(n)nlog(n)

3.如果我们每次求校验值时不用快速排序,而是采用归并排序的思想,只对新增的长度部分排序,然后合并新旧两端,这样总体时间复杂度可以达到 O ( n l o g n ) O(nlogn) O(nlogn)

#include<iostream>
#include<algorithm>
using namespace std;
#define ll long long
#define MAX_N 500000
ll a[MAX_N+5],w[MAX_N+5],temp[MAX_N+5];
int k;
int n,m;
ll t;
bool check(int l,int mid,int r)
{
	for(int i=mid+1;i<=r;i++)
	w[i]=a[i];
	sort(w+mid+1,w+r+1);
	int i=l,j=mid+1,k=l;
	while(i<=mid&&j<=r)
	{
		if(w[i]<=w[j])temp[k++]=w[i++];
		else temp[k++]=w[j++];
	}
	while(i<=mid)temp[k++]=w[i++];
	while(j<=r)temp[k++]=w[j++];
	long long sum=0;
	for(int i=l,j=r,cnt=0;cnt<m&&i<j;cnt++,i++,j--)
	sum+=(temp[i]-temp[j])*(temp[i]-temp[j]);
	if(sum<=t)return 1;
	else return 0;
}
int main()
{
	cin>>k;
	while(k--)
	{
		cin>>n>>m>>t;
		for(int i=1;i<=n;i++)
		scanf("%lld",a+i);
		w[1]=a[1];
		int st=1,ed=1,len=1,ans=0;
		while(ed<n)
		{
			len=1;
			while(len)
			{
				if(ed+len<=n&&check(st,ed,ed+len))
				{
					ed+=len;
					len<<=1;
					for(int i=st;i<=ed;i++)
					w[i]=temp[i];
				}
				else len>>=1;
			}
			st=ed+1;
			ans+=1;
		}
		cout<<ans<<endl;	
	}
	return 0;
}

ST算法

在RMQ问题(区间最值问题)中,著名的ST算法就是倍增的产物。ST算法能在 O ( n l o g n ) O(nlogn) O(nlogn)的时间预处理后,以 O ( 1 ) O(1) O(1)的时间复杂度在线回答“数列A中下标在l~r之间的数的最大值是多少这样的问题”。

预处理:

void ST_prework()
{
	for(int i=1;i<=n;i++)f[i][0]=a[i];
	int t=log(n)/log(2)+1;
	for(int j=1;j<t;j++)
	for(int i=1;i<=n-(1<<j)+1;i++)
	f[i][j]=max(f[i][j-1],f[i+(1<<(j-1))][j-1]);
}

其中 f [ i ] [ j ] f[i][j] f[i][j]表示数列A中的下标在子区间 [ i , i + 2 j − 1 ] [i,i+2^j-1] [i,i+2j1]里的数的最大值。

查询:

int ST_query(int l,int r)
{
	int k=log(r-l+1)/log(2);
	return max(f[l][k],f[r-(1<<k)+1][k]);
}

查询任意区间 [ l , r ] [l,r] [l,r]内的最值时,需要先计算出一个k,满足 2 k ≤ r − l + 1 < 2 k + 1 2^k \leq r-l+1 <2^{k+1} 2krl+1<2k+1, f [ l , k ] f[l,k] f[l,k] f [ r − 2 k + 1 , k ] f[r-2^k+1,k] f[r2k+1,k]中的较大的值就是区间 [ l , r ] [l,r] [l,r]内的最值。

注:log函数效率较高,一般对程序性能影响不大。为了保证复杂度为O(1),可以O(n)预处理出1~n种区间长度各自对应的k值。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值