倍增与ST表闲话

在算法竞赛中,我们一般都被一件事情所困扰:如果从 \(\mathcal O(n^2)\rightarrow \mathcal O(n\log n)\) 呢?
其实很简单。我们发现如果想要有 \(\mathcal O(n\log n)\) 做法,我们一般考虑分治或倍增(即使是线段树等高级数据结构,绝大部分都是分治)。
分治其实也很简单,我们发现如果这一道题可以二进制拆分,我们肯定会对于二进制位做;如果这道题序列有序,我们大概率会去二分,若序列无序,我们就会用分治做法。
而倍增就是一个与分治过程相反的过程,他要求你去二进制扩充。
由于在算法竞赛中,倍增出现次数较少,所以很多人都头疼:什么时候、怎么才能使用倍增算法呢?

先来举个例子。
我们知道,莫队如果能 \(\mathcal O(1)\) 的从 \(l\rightarrow l\pm 1\) 以及 \(r\rightarrow r\pm 1\),我们就可以使用莫队。
我们先来看看倍增的一些性质。我们知道,倍增其实和二分很像。倍增只不过是把二分过程逆序过来。我们可以将 \(2\)\(2^{j-1}\) 的序列的答案合并为一个长度 \(2^j\) 的序列,那么我们就可以使用倍增算法。
那么,我们就发现:1.在一个算法中,我们可以选择一位一位的去扩展合并序列 2.符合可加性(即两个序列答案可合并)。那么我们可以考虑倍增。
我们可以举几个例题。

P3865 【模板】ST 表 && RMQ 问题

这道题虽然叫做 ST 表模板题,但是我们还是可以从中看出倍增的味道,我们来品一品。
首先第一个条件一定符合,即裸的区间 dp 即可。
至于第二个条件,想一想也是符合的。一个序列的最小值,即为左右两半序列的最小值的较小值。
于是,st 表诞生了。

P1081 [NOIP 2012 提高组] 开车旅行

这道题也是很容易看出倍增,我们来看看。
我们发现,我们可以一步一步的从一个城市跳到最后结束的城市,符合倍增第一条性质。
而且我们发现,我们可以把两步和为一块去处理,因为路程是可加的,可以合并。
看到这里直接上倍增做完了,体验水紫的乐趣。
难点在于如何预处理最小值与次小值,我们可以按照海拔排序后建立双向链表,然后我们就可以从表头开始找最小值与次小值。最小值一定是在 \(i\pm 1\) 的位置,次小值同理即可。

P3295 [SCOI2016] 萌萌哒

这道题很新颖。我们先考虑暴力做法。
可以每一次对 \(l1-r1\) 以及 \(l2-r2\) 的每一位置进行并查集合并操作,设集合个数为 \(S\),则最后答案为 \(9\times 10^{S-1}\)
这么做复杂度为 \(\mathcal O(nm)\),考虑优化。
一个最具引导性的事情是:并查集可以合并,并且可以一个元素一个元素的合并!
一眼在并查集上倍增。我们假设 \(f_{x,k}\) 表示以 \(x\) 为起点,长度为 \(2^k\) 的序列在哪一个集合里,然后倍增统计即可,复杂度 \(\mathcal O(n\log m)\)
可能会太抽象,所以赋个代码。

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+5,MOD=1e9+7;
int n,m,f[N][25];
int find(int x,int k)
{
	if(x==f[x][k]) return x;
	return f[x][k]=find(f[x][k],k);
}
void merge(int x,int y,int k)
{
	int fx=find(x,k),fy=find(y,k);
	if(fx!=fy) f[fx][k]=fy;
}
int qsm(int x,int k)
{
	int res=1;
	for(;k;k>>=1,x=x*x%MOD)
		if(k&1) res=res*x%MOD;
	return res;
}
signed main()
{
	cin>>n>>m; 
	for(int j=0;j<=20;j++)
		for(int i=1;i<=n;i++)
			f[i][j]=i;
	for(int i=1;i<=m;i++)
	{
		int l1,r1,l2,r2;
		cin>>l1>>r1>>l2>>r2;
		for(int j=20;~j;j--)
			if(l1+(1<<j)-1<=r1)
				merge(l1,l2,j),l1+=(1<<j),l2+=(1<<j); 
	}
	for(int j=20;j;j--)
		for(int i=1;i+(1<<j)-1<=n;i++)
		{
			int fi=find(i,j);if(fi==i) continue;  
			merge(i,fi,j-1),merge(i+(1<<(j-1)),fi+(1<<(j-1)),j-1);
		}
	int cnt=0;
	for(int i=1;i<=n;i++)
		if(find(i,0)==i)
			cnt++;
	cout<<qsm(10,cnt-1)*9%MOD;
	return 0;
}

P3292 [SCOI2016] 幸运数字 做题记录

这道题是一道线性基合并的题目。
首先发现 LCA 可以倍增实现,而这道题又是集合最大异或和,一眼线性基。
参考并查集倍增,我们可以考虑线性倍增与合并。
我们假设维护一个倍增线性基 \(BASE[x][i]\) 表示从 \(x\) 开始,向上跳 \(2^i\) 个祖先,这一条路径上的线性基。
那么很好转移:\(BASE[x][i]=merge(BASE[x][i-1],BASE[f[x][i-1]][i-1])\)
考虑 merge 函数的实现就是将线性基 \(A\) 的所有元素插入到 \(B\) 内即可。
注意 corner case。如果 \(x=y\),那么要直接输出 \(a[x]\)

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e4+5;
vector<int> G[N];
int n,m,dep[N],f[N][16],a[N];
struct LB
{
	int p[65];
	void clear(){memset(p,0,sizeof(p));}
	void ins(int x)
	{
		for(int i=61;~i;i--)
		{
			if(~(x>>i)&1) continue;
			if(!p[i])
			{
				p[i]=x;
				break;
			}
			x^=p[i];
		}
	}
	friend LB operator +(const LB &A,const LB &B)
	{
		LB T=A;
		for(int i=61;~i;i--)
			if(B.p[i]) T.ins(B.p[i]);
		return T;
	}
}B[N][16];
void dfs(int x,int fa)
{
	f[x][0]=fa,dep[x]=dep[fa]+1;B[x][0].clear();
	B[x][0].ins(a[x]),B[x][0].ins(a[fa]);
	for(int i=1;i<=15;i++)
	{
		f[x][i]=f[f[x][i-1]][i-1];
		B[x][i].clear();
		B[x][i]=B[x][i-1]+B[f[x][i-1]][i-1];
	}
	for(int v:G[x])
	{
		if(v==fa) continue;
		dfs(v,x);
	}
}
LB LCA(int x,int y)
{
	if(dep[x]<dep[y]) swap(x,y);
	LB T;T.clear();
	for(int i=15;~i;i--)
		if(dep[f[x][i]]>=dep[y])
			T=T+B[x][i],x=f[x][i];
	if(x==y) return T;
	for(int i=15;~i;i--)
	{
		if(f[x][i]==f[y][i]) continue;
		T=T+B[x][i]+B[y][i];
		x=f[x][i],y=f[y][i];
	}
	return T+B[x][0]+B[y][0];
}
signed main()
{
//	freopen("a.in","r",stdin);
//	freopen("a.out","w",stdout);
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	cin>>n>>m;
	for(int i=1;i<=n;i++)
		cin>>a[i];
	for(int i=1;i<n;i++)
	{
		int x,y;cin>>x>>y;
		G[x].push_back(y);
		G[y].push_back(x);
	}
	dfs(1,0);
	while(m--)
	{
		int x,y;cin>>x>>y;
		if(x==y)
		{
			cout<<a[x]<<"\n";
			continue;
		}
		LB T;T.clear();
		T=LCA(x,y);
		int res=0;
		for(int i=61;~i;i--)
			res=max(res,res^T.p[i]);
		cout<<res<<"\n";
	}
	return 0;
}
posted @ 2025-03-09 07:54  I_AK_CTSC  阅读(53)  评论(0)    收藏  举报