【算法每日一练]-结构优化 篇3( 倍增) 忠诚 ,国旗计划

今天讲一下倍增

目录

题目:忠诚

 思路:

题目:国旗计划

  思路: 


    

      

           

查询迭代类倍增:

    本质是一个一个选区间使总长度达到 M,类似凑一个数。而我们会经常用不大于它最大的二的次幂,减去之后,再重复这个过程,这样这个数的值会减小得非常快,一共只需要减 log(num) 次就可以凑出。(国旗计划)

       

       

题目:忠诚

     

思路:

       

很明显是一道区间最值的问题:也就是著名的RMQ(Range Minimum/Maximum Query)区间最值查询问题(最好会背啊!)

      

首先设置f[i][j]表示从下标i走2*j长度之间的最值,然后依此创建ST表,最后RMQ查询ST表即可。

根据这个转移式子f[i][j]=min( f[i][j-1] , f[i+(1<<(j-1))][j-1]) 枚举每个j和i即可

其中f[i][j]表示i+(0 ~ 2^j - 1)区间,长度恰好2^j

f[i][j-1]表示i+(0 ~ 2^(j-1) - 1)区间,长度恰好2^(j-1)

f[i+(1<<j-1][j-1]表示i+(2^(j-1) ~ 2^j -1)区间,长度恰好2^(j-1)

     

       

RMQ函数中有一个易错点:

return min(f[l][k],f[r-(1<<k)+1][k]);这个是正确的,

return min(f[l][k],f[l+(1<<k)][k]);这个是错误的。(原因是可能覆盖不到原区间右端点)

明显k=log2(r-k+1),而f[l][k]维护的区间是l+[0~2^k-1],f[r-(1<<k)+1][k]维护的区间是r+[-(2^k)+1,0]

我们只需要保证r-2^k+1<=l+2^k-1成立即可保证结果的正确性。

我们来证明一下:

r-l+2<=2*(2^k)

因为k=log2(r-l+1), 则r-l+2<=2*(r-l+1)

则r-l+2<=2*(r-l)+2

即r-l<=2*(r-l)

所以r-l>=0恒成立。所以原命题成立

#include<bits/stdc++.h>            
using namespace std;
#define maxn 100005
int n,m,l,r,a[maxn],f[maxn][22]; //f[i][j](ST表)表示从下标i走2*j长度之间的最值
int RMQ(int l,int r)//RMQ(Range Minimum/Maximum Query)区间最值查询
{
	int k=log2(r-l+1);//r-l+1是区间长度
	return min(f[l][k],f[r-(1<<k)+1][k]);
//log10(r-l+1)是取以10为底的对数,以此类推
}
void ST_create(){//创建ST表
	for(int i=1;i<=n;i++)	f[i][0]=a[i];//初始化
	int k=log2(n);
	for(int j=1;j<=k;j++){//j是二进制大小
		for(int i=1;i<=n-(1<<j)+1;i++){//对每个点遍历,区间长度是2^j,所以终点是i+2^j-1
			f[i][j]=min(f[i][j-1],f[i+(1<<(j-1))][j-1]);
		}
	}
}
int main()
{
	scanf("%d%d",&n,&m);//账数和问题数
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	ST_create();
	for(int i=1;i<=m;i++)
	{
		scanf("%d%d",&l,&r);
		printf("%d ",RMQ(l,r));
	}
	return 0;
}

      

      

题目:国旗计划

       

思路: 

        

      首先这个题可以抽象成在1~m的数轴上进行转移的问题(当然有n个转移点),但是我们要注意到题上说的顺时针转移,那么不难想到“ 破环成链 ”,这样的话这个问题就解决了。

     

     然后是我们知道处理当前区间的时候,不难发现只需要找到当前区间内可转移到区间外最远点的起始点就是最优转移方式(这个很好想到),然后我们转移一次就记录一次个数,跑够m即可,这样就找到了总需要的最少人数。

    

       

好了,下面是重点!!!

      

暴力做法O(n^2):因为每个区间要转移的下一个最优区间一定是固定的,我们先预处理一下每个区间转移后的下一个区间。然后以每个人为起点,对跑m个长度的所需人数统计即可。非常耗时。

     

其实你仔细想的话会发现上个想法很蠢,就类似“树”一样,你要到某一个长度的节点,为什么非要一次走只一个节点呢,倍增逼近多快呀

      

倍增做法O(n):对每个区间预处理转移i个区间能到的区间(打表),然后一个一个选区间使总长度达到给定值,即可很快找到需要的人数。

     

然后有一个要注意的地方:

        

求f[i][0]即每个区间后选的第一个区间时候,肯定不能两重循环,那时间复杂度就再次变为 O(N^2)。     

      

这个时候利用题目中提到的一个性质:“每名边防战士的奔袭区间都不会被其他边防战士的奔袭区间所包含 ”   (双指针出场

      
则对于单调递增l的, r也单调递增,我们只需要找到满足j.l<=r.i 的最后一个区间即可,因此使用双指针,时间复杂度降为 O(N)。 

      

#include<bits/stdc++.h>           //国旗计划(环形线段覆盖)(注意线段不会包含)
using namespace std;
#define ll long long
const int N=2e5+10;
int n,m,ans[N];
int st[20][N<<1],s[20][N<<1];//st[i][j]表示从j点为起点的进行2^i次迭代的起点的下标(自身不算)
struct node {int l,r,id;}a[N<<1];
bool cmp(node x,node y){
	return x.l<y.l; //线段不会完全包含,所以l单增即r单增
}
void ST_create(){
	for(int i=1,j=1;i<=2*n;i++){//(双指针快速寻找)
		while(j<=2*n&&a[j].l<=a[i].r)j++;//寻找范围内下一个起点
		st[0][i]=j-1; 
	}
	for(int i=1;i<=19;i++) //枚举二进制i
		for(int j=1;j<=2*n;j++) //对每个起点j遍历
			st[i][j]=st[i-1][st[i-1][j]];
}
void search(){
	for(int i=1;i<=n;i++){  
		int up=a[i].l+m,an=0,p=i;
		for(int j=19;j>=0;j--)
			if(st[j][p]&&a[st[j][p]].r<up) //注意st不可以等于0
				an+=1<<j,p=st[j][p]; 
		ans[a[i].id]=an+2;//因为本来就没算本身,然后也不算入终点,所以加2
	}
}
int main(){
	cin>>n>>m; int l,r; //n是边防战士数,m是边防站数
	for(int i=1;i<=n;i++){
		scanf("%d %d",&l,&r);if(l>r) r+=m;//对战士的覆盖范围破链成环
		a[i].l=l,a[i].r=r,a[i].id=i;//标记每个战士编号
	}
	sort(a+1,a+n+1,cmp); 
	for(int i=1;i<=n;i++) {//把后半段的战士的区间标记一下,编号就不用了
		a[i+n].l=a[i].l+m,a[i+n].r=a[i].r+m; 
	}
	ST_create(); //创建ST表
	search();	//对每个点进行查询
	for(int i=1;i<=n;i++)
	printf("%d ",ans[i]);
	return 0;
}

      

      

可以总结一下倍增使用的场合:
1.(最值类)RMQ区间最值
2.(迭代类)同一件事完成确定次数。可以先打表迭代,然后使用倍增进行长度逼近。

      

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值