倍增 ——2

吐槽一下这怎么能出一个系列呢,草问题在于我刷的还是太少啦,相对来说这一篇的题目应该会简单一点?
P3398 仓鼠找 sugar虽然说这些题实在和倍增关系不大,就用个lca处理,不过还是有刷的必要。
有一个神奇的规律:如果两条路径相交,那么一定有一条路径的LCA在另一条路径上,还有就而判断一个节点x,是否在路径s-t上需要满足如下几个条件:

deep[x]>=deep[LCA(s,t)]
LCA(s,x)=x或LCA(t,x)=x;

再说一遍:时间要… no,是如何判断一点在一条路径上呢,当然是路径的长度等于该点到路径两端的距离啦,这个简单,因为是树嘛。开始!

#include<bits/stdc++.h>
using namespace std;
int n,m;
int last[200000],len=0;
int db[200000][30],dep[200000];
struct pp
{
	int x,y,next;
};pp p[800000];
void ins(int x,int y)
{
	int now=++len;
	p[now]={x,y,last[x]};last[x]=now;
	return ;
}
void getdb(int x,int fa)
{
	db[x][0]=fa;dep[x]=dep[fa]+1;
	for(int i=1;(1<<i)<=dep[x];i++) db[x][i]=db[db[x][i-1]][i-1];
	for(int i=last[x];i!=-1;i=p[i].next)
	{
		int y=p[i].y;
		if(y!=fa) getdb(y,x);
	}
	return ;
}
int getlca(int x,int y)//破防了lca打炸了nmd我服了我自己我是zz吧
{
	if(dep[x]>dep[y]) swap(x,y);
	for(int i=20;i>=0;i--) 
	{
		if(dep[x]<=dep[y]-(1<<i)) y=db[y][i];
	}
	if(x==y) return x;
	for(int i=20;i>=0;i--) 
	{
		if(db[x][i]!=db[y][i]) x=db[x][i],y=db[y][i];
	}
	return db[x][0];
}
int dis(int x,int y)
{
	int lca=getlca(x,y);
	return abs(dep[lca]-dep[x])+abs(dep[lca]-dep[y]);
}
int main()
{
	memset(last,-1,sizeof(last));	
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n-1;i++) 
	{
		int x,y;scanf("%d%d",&x,&y);
		ins(x,y);ins(y,x);
	}
	getdb(1,0);//printf("*");
	while(m--)
	{
		int a,b,x,y,c,d;scanf("%d%d%d%d",&a,&b,&c,&d);
		x=getlca(a,b),y=getlca(c,d);
		if(dis(a,y)+dis(b,y)==dis(a,b)||dis(c,x)+dis(d,x)==dis(c,d)) printf("Y\n");
		else printf("N\n");
	}
	return 0;
}

下一道倍增是极好的了,P3509 [POI2010]ZAB-Frog充斥了一种妙不可言的美,对区间取k距离的优化,以及倍增的思路,采用滚动数组的优化,算是极其厉害新奇的了。细说一番转移。神奇呀!

	while(m!=0)
	{
		if(m&1!=0) 
		{
			for(int i=1;i<=n;i++) ans[i]=f[ans[i]]; 
		}
		m>>=1;
		for(int i=1;i<=n;i++) ff[i]=f[i];
		for(int i=1;i<=n;i++) f[i]=ff[ff[i]];
	}

首先,这其实和那个快速幂有异曲同工之处,看出来了吧,然后就是那个转移:for(int i=1;i<=n;i++) ff[i]=f[i]; for(int i=1;i<=n;i++) f[i]=ff[ff[i]];很强啊,好好看看,学不会的啦~哈哈开玩笑,自己画图好好想代码:

#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,m,k;
int a[2000000],ans[2000000];
int f[2000000],ff[2000000];
signed main()
{
	scanf("%lld%lld%lld",&n,&k,&m);
	for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
	int l=1,r=k+1;f[1]=k+1;
	for(int i=2;i<=n;i++)
	{
		while(r+1<=n&&a[i]-a[l]>a[r+1]-a[i]) l++,r++;//维护区间,用单调队列的思想
		//具体而说,这种维护方式不会超长因为是区间移动,第二这个维护的是区间第k的距离嘛
		//所以就是如果第k的距离是可以被替代的,那明显会被替代掉咯,你懂了吗?
		//如果无法替代,那么区间明显是不用变的。单调队列新奇的操作呢 
		if(a[i]-a[l]>=a[r]-a[i]) f[i]=l;
		else f[i]=r; 
	}
	for(int i=1;i<=n;i++) ans[i]=i;
	while(m!=0)
	{	
		if(m&1) 
		{
			for(int i=1;i<=n;i++) ans[i]=f[ans[i]]; 
		}
		m>>=1;
		for(int i=1;i<=n;i++) ff[i]=f[i];
		for(int i=1;i<=n;i++) f[i]=ff[ff[i]];
	}
	for(int i=1;i<=n;i++) printf("%lld ",ans[i]);
	return 0;
}

今天就搞到这嗯这是第二天的题,小说一下就是倍增其实和树剖的作用有些重复,所以这两天刷的哈哈哈哈哈哈哈可能混着来吧。P4281 [AHOI2008]紧急集合 / 聚会呃啊这题一眼就看到结论了呢,这东西一看就和lca有关,直接胡一个结论三个点的交点会在其中两个的lca上,那么就可以做了,不过还是可以简化一下,模拟一下图会发现有两个lca是重合的,所以化简成两个lca比较,那么进一步的结论就是与需要比较的两个点中,其实不重合的那个点解是最优的,为什么呢考虑两个lca之间的路径,那么若选择那个重合的,那lca之间的路径会被走两次,反之则只用走一次。懂?于是最终的路径长度通过之前讲的到根的距离即可求。提一下,就是dep[a]+dep[b]+dep[c]-dep[lca1]-dep[lca2]-dep[lca3],为何如此?首先考虑深的那个那么只会被计入一次,然后是lca到lca的路径,然后再到上面的lca到c,那么手推一下即可,重要的在于如何得出的结论:减去一个深的,为的是深的点到lca的路径,减去其中一个浅的,为的是lca到lca的路径,再减去一个浅的,为的是浅的点到lca的路径。
代码:

#include<bits/stdc++.h>
using namespace std;
int n,m;
int len=0,last[1000001];
int db[1000001][30],dep[1000001];
struct pp
{
	int x,y,next;
};pp p[1000001];
void ins(int x,int y)
{
	int now=++len;
	p[now]={x,y,last[x]};last[x]=now;
	return ;
}
void getdb(int x,int fa)
{
	dep[x]=dep[fa]+1;db[x][0]=fa;
	for(int i=1;(1<<i)<=dep[x];i++) db[x][i]=db[db[x][i-1]][i-1];
	for(int i=last[x];i!=-1;i=p[i].next)
	{
		int y=p[i].y;if(y==fa) continue ;
		getdb(y,x); 
	}
	return ; 
}
int getlca(int x,int y)
{
	if(dep[x]>dep[y]) swap(x,y);
	for(int i=20;i>=0;i--) 
	{
		if(dep[x]<=dep[y]-(1<<i)) y=db[y][i];
	}
	if(x==y) return x;
	for(int i=20;i>=0;i--)
	{
		if(db[x][i]!=db[y][i]) x=db[x][i],y=db[y][i];
	}
	return db[x][0];
}

int main()
{
	memset(last,-1,sizeof(last));
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n-1;i++)
	{
		int x,y;scanf("%d%d",&x,&y);
		ins(x,y);ins(y,x);
	}
	getdb(1,0);
	while(m--)
	{
		int a,b,c;scanf("%d%d%d",&a,&b,&c);
		int ans,lca1=getlca(a,b),lca2=getlca(b,c),lca3=getlca(a,c);
		if(lca1==lca2) ans=lca3;
		else if(lca1==lca3) ans=lca2;
		else ans=lca1;
		printf("%d %d\n",ans,dep[a]+dep[b]+dep[c]-dep[lca1]-dep[lca2]-dep[lca3]);
	}
	return 0;
}

下一个P4403 [BJWC2008]秦腾与教学评估说起来今天遇到她了呢,运气真不错!这题的话考虑一个超时暴力,不过好像是二分,倍增好像麻烦的。代码贴另一边吧。放一个大佬的倍增思路:在这里插入图片描述
下一个P2597 [ZJOI2012]灾难这个题目的话,看了一会才懂,是这样的,题目首先保证的没有环,那么明显是要用那种呃bfs,拓扑的方式去处理,具体就是最后一步求前缀和这个暂不表,考虑一个动物会对什么造成贡献,那么当然是它所有食物的lca咯,因此我们把每个节点都连接在它的所有食物的LCA上,即每个节点的父节点是它的所有食物的LCA。然后怎么接呢,前提条件是把某个节点加入树中时它的所有食物已经都在树中了,所以我们决定用拓扑的方式,并将每一个点和它的食物的lca连边,随后求一个前缀和。注意,为什么要这样记录深度呢,因为每一个点它的父亲深度可能会不同,但是它所有父亲的lca是确定的,所以用这个lca加一准没错。而且其实每一个点的食物的lca是确定的。麻烦死了

#include<bits/stdc++.h>
using namespace std;
int n,m,len=0,len1=0;
int to[100001],tot=0;
int db[100001][30],dep[2000001],in[2000001];
struct pp
{
	int x,y,next;
};pp p1[2000001],p[4000001];
int last[1000001],last1[2000001];
void ins1(int x,int y)
{
	int now=++len1;
	p1[now]={x,y,last1[x]};last1[x]=now;
	return ;
}
void ins(int x,int y)
{
	int now=++len;
	p[now]={x,y,last[x]};last[x]=now;
	return ;
}
void getdb(int x)
{
	for(int i=1;i<=20;i++) db[x][i]=db[db[x][i-1]][i-1];
	return ;
}
int getlca(int x,int y)
{
	if(dep[x]<dep[y]) swap(x,y);
	for(int i=20;i>=0;i--) 	
	{
		if (db[x][i]!=0&&dep[db[x][i]]>=dep[y]) x=db[x][i];
	}
	if(x==y) return x;
	for(int i=20;i>=0;i--)
	{
		if(db[x][i]!=0&&db[y][i]!=0&&db[x][i]!=db[y][i]) x=db[x][i],y=db[y][i];
	}
	return db[x][0];
}
int q[1000001],st=1,ed=1;
void gettoq()
{
	for(int i=1;i<=n;i++)
	{
		if(in[i]==0) q[ed++]=i;
	}
	while(st!=ed)
	{
		int x=q[st++];to[++tot]=x;
		for(int i=last1[x];i!=-1;i=p1[i].next)
		{
			int y=p1[i].y;in[y]--;
			if(in[y]==0) q[ed++]=y;
		}
	}
	return ;
} 
void bd()
{
	dep[n+1]=1;db[n+1][0]=n+1;
	for(int i=n;i>=1;i--)
	{
		int x=to[i];
		if(last1[x]==-1) //如果这个点是生产者~ 
		{
			db[x][0]=n+1;dep[x]=2;
			ins(n+1,x);
			continue ;
		}	
		int lca=p1[last1[x]].y;
		for(int j=last1[x];j!=-1;j=p1[j].next) lca=getlca(lca,p1[j].y);
		db[x][0]=lca;ins(lca,x);dep[x]=dep[lca]+1;getdb(x);
	}
	return ;
}
int sum[655341];
void getsum(int now)
{
	sum[now]=1;
	for(int i=last[now];i!=-1;i=p[i].next)
	{
		int y=p[i].y;
		getsum(y);sum[now]+=sum[y]; 
	}
	return ;
}
int main()
{
	memset(last,-1,sizeof(last));memset(last1,-1,sizeof(last1));
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		int x;scanf("%d",&x);
		while(x!=0)
		{
			ins1(i,x);in[x]++;
			scanf("%d",&x);
		}
	}	 
	gettoq();bd();getsum(n+1);
	for(int i=1;i<=n;i++) printf("%d\n",sum[i]-1);//减去自己 
	return 0;
}

好的,做一个:Minimal Segment Cover,这个捏,我看出就和之前那个国旗是类似的,再总结一下,就是屏幕上的线段问题其实都可以考虑倍增维护,更进一步言,维护方式大部都需要从前一步往后推进,例如本题的: for (int i = 1;i <= N;i++) dp[i][0] = max(dp[i][0],dp[i - 1][0]);用,为什么捏,体会一番,因为它只给出了线段端点,所以无点的点便也无可奈何(除非你离线离散化),于是便以向上继承的方式来做(或者向下)。嗯…再想想?倍增好像也就这些套路了罢,预处理,倍增,求值。那么这题的做法其实也说的差不多了:再提两点一个是其枚举的范围其实是x,y的范围,第二是如何判断-1,我们可以不做到y而是做到y-1,然后db[x][0]能到y的话就可以,不然就不行。呃还有就是可能会查到一些不存在的位置嘛?其实没有的因为db[k][0]=k的话那么db[db[k][0]][0]=k;以此类推就可以。

#include<bits/stdc++.h>
using namespace std;
int n,m,k=1e5*5+1;
int db[500005][21];
int main()
{
	memset(db,0,sizeof(db));
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
	{
		int x,y;scanf("%d%d",&x,&y);
		db[x][0]=max(db[x][0],y);
	}
	for(int i=1;i<=k;i++) db[i][0]=max(db[i-1][0],db[i][0]);
	for(int j=1;j<=20;j++)	
	{
		for(int i=0;i<=k;i++) db[i][j]=db[db[i][j-1]][j-1];
	} //为什么这步不用最大值?因为第一步是最优的,所以后面用第一步的也肯定是最优的 
	while(m--)
	{
		int x,y,ans=0;scanf("%d%d",&x,&y);
		for(int i=19;i>=0;i--)
		{
			if(db[x][i]<y) ans+=(1<<i),x=db[x][i];
		}
		if(db[x][0]>=y) printf("%d\n",ans+1);
		else printf("-1\n");
	} 
	return 0;
}

我很不服,我还在想着那件事~呃啊Chips on a Board这一题看不懂:在这里插入图片描述具体言,我们考虑每一个的呃贡献,挺神奇的,本来不大能处理的QWQ,有意思,发现每一个棋子的纵坐标位于i至 i+2^k-1,贡献也一定在这一个区间中,不妨钦定左边界为 i 的距离异或和。考虑转移,发现其最高位的贡献是独立的(即 k 更低的位都不会对这一位产生影响),所以若是从2 ^ (k-1) 转移至 2 ^ (k)-1的贡献明显只有这一段区间中的数的和的奇偶性
明显可以处理一个前缀和来确定每一段区间内的数所以大佬说:

在这里插入图片描述
然后这个倍增表可以在mlogm的时间内处理,然后发现一个神奇的东西,每拼接上一段,整个区间中还未被拼接上的部分的所有棋子的距离都会增加一个二的幂次。换句话说,这只对要求的答案的某一位产生贡献。呃啊那么就这样?神奇博弈论!哦提一下,这个模型是nim游戏,所以若x1 ^ x2 ^ x3…==0 则后手胜 反则先手。简单想想,其实每一段的贡献是分开的,但将其合起时便也美妙起来了~

#include<bits/stdc++.h>
using namespace std;
int n,m;
int sum[300001],a[300001],db[300001][30];
int ans1[300001]; 
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]),sum[a[i]]++;
	for(int i=1;i<=m;i++) sum[i]+=sum[i-1];
//	int k=log2(m)+1;
	for(int j=1;j<=21;j++)
	{
		for(int i=1;i+(1<<j)-1<=m;i++) 
		{
			db[i][j]=db[i][j-1]^db[i+(1<<j-1)][j-1]^((1<<j-1)*((sum[i+(1<<j)-1]-sum[i+(1<<j-1)-1])&1));
			//这样的转移你学会了嘛? 
			//其实若你再不懂的话,想一想,后 i+(1<<j-1)-1到 i+(1<<j)-1这一段,给前面那一段的距离的贡献是没有算的
			//所以就要乘上一个 (1<<j-1)啦~ 
		}
	}
	int q;scanf("%d",&q);
	for(int qq=1;qq<=q;qq++)
	{
		int l,r,ans=0;scanf("%d%d",&l,&r);
		for(int j=21;j>=0;j--)
		{
			if(l+(1<<j)<=r)
			{	
				ans^=db[l][j],l+=(1<<j);
				if((sum[r]-sum[l-1])&1) ans^=(1<<j);//如果这一位有贡献(单独的) 
				//其实就是((sum[i+(1<<j)-1]-sum[i+(1<<j-1)-1])&1)); 
			}
		}
		ans1[qq]=ans;
	}
	for(int i=1;i<=q;i++) 
	{
		if(ans1[i]==0) printf("B");
		else printf("A");
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值