在算法竞赛中,我们一般都被一件事情所困扰:如果从 \(\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] 萌萌哒
](https://www.luogu.com.cn/problem/P3295)
这道题很新颖。我们先考虑暴力做法。
可以每一次对 \(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;
}