PPT上倍增的例题

AtCoder - abc167_d图上倍增
注意:这道题倍增的方法只放代码,我来讲讲本菜鸡写的另一种方法,就是找环,这个方法的预处理时复为O(n),查找时复也为O(n),虽然查找比倍增慢,但是预处理却比倍增快,而且对空间的需求也是n,所以更加适合。放一下时间对比图(下面是倍增,上面是找环)。
在这里插入图片描述
思路:观察题目,给出n个数,分别对应每个城市可以传送到哪一个城市,这其实是个啥,就是n个点,然后每个点可以连别的点,n个点,一共n-1
个区间,所以有一个点一定会连一个之前出现过的点。
例如
在这里插入图片描述
红线为5可以传的点无论怎么连,一定会成环。
我们用数组a来记录每个城市能传到哪,起点是从一开始,这时候有两种情况,1个就是查找次数k小于等于找到换所需的次数circle,那么只需在循环到k时直接输出就行,另一个就是k大于circle,那么就让k=k%circle,因为转圈没有任何意义,注意记录一下第一次找到环的起点时的次数,然后用第二次到该点的次数减一下才是circle。下面放代码。

#include<bits/stdc++.h>
#define M 200006
#define ll long long int
using namespace std;
int arr[M],n,last=1,circle=0;//last 记录上一次传送到的终点,也就是本次传送的起点
ll m;
struct node{   //s记录访问该点的次数,t记录第s次访问到该点时的传送次数
	int s,t;
}p[M];
int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;++i)  
	{
		cin>>arr[i];
	}
	p[1].s=1;    //初始化,一开始起点为1,传送次数为0
	p[1].t=0;
	for(int i=1;i<=n;++i)
	{
		p[arr[last]].s+=1;   //传到的点访问次数+1
		if(i==m){cout<<arr[last];return 0;}//如果找到circle前就不传了直接输出
		if(p[arr[last]].s==2) //当一个点被访问第二次时,circle找到了
		{  
			circle=i-p[arr[last]].t;//此时circlr的起点的第一次被访问的传送次数还未更新所以直接减掉就是circle
			m-=p[arr[last]].t; //总访问次数也减去不在环内的部分
			break;
		}
		p[arr[last]].t=i;//记录传送次数
		last=arr[last];//更新起点
	}
	m%=circle;
	if(m==0){cout<<arr[last];return 0;}//若整除,则终点就是环的起点
	last=arr[last];//更新起点为环的起点
	for(int i=1;i<=m;++i)
	{
		last=arr[last];
	}
	cout<<last;
}

倍增的代码

#include<bits/stdc++.h>
#define M 200006
#define ll long long int
using namespace std;
int c[M][66];
int n,goal=1,t;
ll m;
void bz(){
	int len=log(m)/log(2)+1;
	for(int i=1;i<=len;++i)
	for(int j=1;j<=n;++j)
	{
		c[j][i]=c[c[j][i-1]][i-1];
	}
}
int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;++i)
	{
		cin>>c[i][0];
	}
	bz();
	while(m){
		if(m&1){
			goal=c[goal][t];
		}
		m/=2;
		++t;
	}
	cout<<goal;
}

Acwing 1270 数列区间最大值
这题模板题,因为输入数据很多所以用个快读优化

#include<bits/stdc++.h>
#define M 190006
using namespace std;
int n,m,c[M][30],mx;

int read(){
	int f=1,s=0;
	char ch=getchar();
	while(ch<'0'||ch>'9'){ch=='-'?-1:1;ch=getchar();}
	while(ch>='0'&&ch<='9'){s=(s<<3)+(s<<1)+ch-'0';ch=getchar();}
	return f*s;
}
int max(int a,int b)
{
	if(a>b)return a;
	return b;
}
void bz()
{
	int len=log(n)/log(2)+1;
	for(int i=1;i<=len;++i)
	for(int j=1;j<=n;++j)
	{
		c[j][i]=max(c[j][i-1],c[j+(1<<i-1)][i-1]);
	}
}
int main()
{
	n=read();
	m=read();
	for(int i=1;i<=n;++i)
	{
		scanf("%d",&c[i][0]);
	}
	bz();
	for(int i=1;i<=m;++i)
	{
		int a,b;
		a=read();
		b=read();
		int len=log(b-a+1)/log(2);
		mx=max(c[a][len],c[b-(1<<len)+1][len]);
		printf("%d\n",mx);
		mx=0;
	}
}

牛客竞赛15429
好吧这道题是这个PPT里我唯一看了题解的题,em,说实话,因为以为是按区间倍增的,所以死都出不来,没想到是按分段次数倍增的,如果有人第一次做就AC的话,那是真的NP,受本菜鸡一拜,orz。我的代码大致把看的几个题解的优点结合了一下,毕竟要办事的嘛,总不能真就白嫖吧。
思路
预处理: 就是以每个点为起点,然后按可以分的区间数倍增,用二分查找(upper_bound()函数)去记录这一段和刚好大于k的那个点记录一下。(其实光是这个设计就很妙了,因为如果用的lower_bound,那么你的数组存的点就有可能是刚好等于k的或是大于k的点,会对后面的查询造成影响)。这个查找用前缀和优化一下,注意:预处理的时候一定要将c[n+1][0]初始化为n+1,因为当最后查不到比k大的区间和时upper_bound()会返回c.end(),所以此时的起点就会变成n+1,如果不加的话,c数组里就会有一群查到最后然后疯狂等于0的数据,然后你的查询就上天了

查询思路贪心):先说输出无法分段的情况,同样的前缀和(前缀和NP),用数组b记录下到第几个数时数组中大于k的数的数量,然后用查询所给的右区间减去(左区间减1)的值,若b[r]-b[l-1]>0就直接输出Chtholly。
接下来就是另一个很妙的了,我们从j=logn(n是最大值)开始遍历c[l][j],如果c[i][j]<=r就给总段数加上(1<<j)然后更新起点,不然就直接j–,(为啥呢?我也不知道 ,你想如果c[l][j]=r+2,r+1的区间上的数刚好是k,那就多算了一次对吧,那不行呀,我们要贪呀,多一次都不干,提醒:切记c数组返回的是另一段的起点,所以等于r时也不能结束循环,因为r那个位置还没验货)这里为什么妙?(因为这里只需要一次for就行了,而不需要每个起点都从logn开始,why?废话 ,c[l][j]>r和c[c[l][j-1][j-1]>r不是一个东西吗?所以只需要遍历一次就行了。
还有一个坑哦(我找了好久,哎,粗还是我粗呀),那就是当j循环到0时它不一定到r的,(自己带样例吧,我写不动了┭┮﹏┭┮)所以最后判断一下l是否<=r,如果小于就+1。
啊啊啊啊啊啊,终于结束了呀呀呀呀呀呀!!!!
哦,对了还有代码

#include<bits/stdc++.h>
#define ll long long int
#define M 1000006
using namespace std;
int arr[M],c[M][21],re[M],n,m;
ll s[M],k;
int read()
{
	char ch=getchar();
	int f=1,sum=0;
	while(ch<'0'||ch>'9'){f=ch=='-'?-1:1;ch=getchar();}
	while(ch>='0'&&ch<='9'){sum=(sum<<3)+(sum<<1)+ch-'0';ch=getchar();}
	return f*sum;
}
void bz()
{
	for(int i=1;i<=n;++i)c[i][0]=upper_bound(s+1,s+1+n,s[i-1]+k)-s;
	c[n+1][0]=n+1;
	for(int i=1;i<=20;++i)
	for(int j=1;j<=n+1;++j)
	{
		c[j][i]=c[c[j][i-1]][i-1];
	}
}
int main(){
	n=read(),m=read(),k=read();
	for(int i=1;i<=n;++i)
	{
		arr[i]=read();
		s[i]+=s[i-1]+arr[i];
		if(k<arr[i])re[i]=re[i-1]+1;
		else re[i]=re[i-1];
	}
	bz();
	for(int i=1;i<=m;++i)
	{
		int l,r,sum=0;
		l=read(),r=read();
		if(re[r]-re[l-1]>0){printf("Chtholly\n");continue;}
		for(int j=20;j>=0;--j)
		{
			if(c[l][j]<=r){l=c[l][j];sum+=(1<<j);}
		}
		if(l<=r)sum++;
		printf("%d\n",sum);
	}
}
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 1024 设计师: 上身试试
应支付0元
点击重新获取
扫码支付

支付成功即可阅读