倍增啊倍增

倍增法

本质:吾以为倍增法本质就是将二分的过程存了下来,二分本来是个递归的过程嘛,倍增法就是将其变成递推了。比如二分可以将其划为两个子问题求解,这两个子问题可以是重复性贡献,也可以是不重复性贡献的,然后再用递推式将其求出来。更详细的理解可以见例题
倍增法本身的复杂度是O(nlogn)的,然后可以O(1)的查询,所以倍增法适用于多次询问的情况,如果只有1次询问的话,就没必要倍增了,直接算就行了。但倍增本身复杂度也不高,O(nlogn),也可以用来解决1次询问情况,不过就是有些大材小用了。

ST表

模板

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll maxn=1e5+11;
ll n,ans[maxn*10],st[maxn][17];//2的17次方比1e5大一点
//求区间最大值
void init(){//st表初始化
    for(ll j=1;(1<<j)<=n;j++)
        for(ll i=1;i+(1<<j)-1<=n;i++)
            st[i][j]=max(st[i][j-1],st[i+(1<<j-1)][j-1]);
//划分为两个子问题求解,这两个子问题是可重复性贡献的
}
ll query(ll l,ll r){//区间[l,r],注意是闭区间,返回区间[l,r]的最大值
    ll k=0;while(l+(1<<k+1)-1<r)k++;
    return max(st[l][k],st[r-(1<<k)+1][k]);//不直接找一个大区间覆盖而是要找两个小区间合并是因为大区间可能超过了st表的长度
}

ST表-二维RMQ

ST表-二维RMQ

LCA-最近公共祖先

P3379 【模板】最近公共祖先(LCA)
模板
这个模板注意下dfs里面fa初始化的问题,要么直接设置为常数,要么就改为i<=lg2[d[now]-1],然后再在lca函数的if改成if (fa[x][k] != fa[y][k] && k <= lg2[d[x] - 1])

/*** 
 * @Practice Win
 * 洛谷 P3379 【模板】最近公共祖先(LCA)
 */
#include<bits/stdc++.h>
#pragma comment(linker, "/STACK:102400000,102400000")//解决递归函数多次调用栈溢出问题
using namespace std;
#define Debug(x) cout<<#x<<':'<<x<<endl
#define INF 0x7fffffff//10^9级别,不到2^32
typedef long long ll;
const ll maxn=5e5+11;
struct Edge{
	ll to,next;
}edge[maxn*2];//edge的边数要乘2
ll head[maxn],d[maxn],fa[maxn][22],lg2[maxn],tmp=1;
//d[i]是结点i的深度,fa[i][j]表示i的第2的j次方父结点,lg2[i]是log2(i)取整数
void add(ll u,ll v){
	edge[tmp].to=v;
	edge[tmp].next=head[u];
	head[u]=tmp++;
}
void dfs(ll now,ll far){//预处理d与fa数组
	fa[now][0]=far,d[now]=d[far]+1;
	for(ll i=1;i<=20;i++)  fa[now][i]=fa[fa[now][i-1]][i-1];//这个常数20对应的是1e6!!!!!!!!!!
	//不用d[now]是因为多样例根节点不同的时候,因为在lca那个if里面k可能会比较大,然后指向不一样
	for(ll i=head[now];i;i=edge[i].next){
		if(edge[i].to!=far)   dfs(edge[i].to,now);//这里记得加个条件,不能到父节点
	}
}
ll lca(ll x,ll y){//输入两个结点编号,x与y,返回x和y的lca
	if(d[x]<d[y])  swap(x,y);
	while(d[x]>d[y])  x=fa[x][lg2[d[x]-d[y]]];//这里要while,因为这里的lg2不是精确值
	//这里re找了我好久错误!!!原来不是数组开太大了,也不是数组开太小了,是数组里的结果有负数!!!
	if(x==y)  return x;
	for(ll k=lg2[d[x]-1];k>=0;k--){
		if(fa[x][k]!=fa[y][k]){//找到lca的下一层
			x=fa[x][k],y=fa[y][k];
		}
	}
	return fa[x][0];
}
void init(){
	for(ll i=2;i<maxn;i++)  lg2[i]=lg2[i/2]+1;
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
	ll n,m,s;  cin>>n>>m>>s;//n个 结点,m次询问,以s为根节点
	init();
	for(ll i=1,u,v;i<=n-1;i++){
		cin>>u>>v;  add(u,v);add(v,u);//无向图!!!
	}
	dfs(s,0);
	for(ll i=1,u,v;i<=m;i++){
		cin>>u>>v;  cout<<lca(u,v)<<endl;
	}
    return 0;
}


RMQ灵活运用,求区间最常出现数字的数量

Frequent values POJ - 3368
题意:给一个数列n,n是非递减序列,有重复数字。有q次询问,n,q<1e5,每次问在区间[l,r]内最经常出现的数字的数量。
思路:将连续相同的数字看成一个块,预处理出每个块含有的数字的数量,以及块的右坐标。这个预处理出来的值可以封装成结构体减少空间。也可以直接在原数组的基础上,加两个数组num[i]表示i所处块含有的数字数量,pos[i]表示i映射到st表中的坐标。一开始我是用的第一个方法,麻烦了些,看了别人的题解知道第二个方法,确实方便些,直接以空间换时间,反正ACM竞赛嘛。对于每次查询,可以将答案分成三部分,左边残余块,中间连续一整块,用RMQ处理,右边残余块,取三者最大值即可。
踩坑:这个方法不适用于所有情况,就要分类讨论,不要怕麻烦,如果这个方法不适用于所有情况,那就得分类讨论,不然偷懒的话,找BUG的时间你知道的,都够再写道题了。比如这里很明显,不是每次都有中间区间,如果每次都按有中间区间来算,就错了。可能l,r在一个块内,没有中间区间,l,r在两个相邻的块,也没有中间区间,当l,r中间隔一个块时,才有中间区间。然后这里又设计左右坐标的加减,没有一个统一的方法处理,很明显需要分类讨论。

LCA灵活运用,求树上两点间距离

How far away ? HDU - 2586
题意:给你一个无向树,求任意两点间距离,多次询问。
思路:统一以某个点为根节点,记录每个点到根节点的距离,两点之间距离就等于lca到两点的距离之和。

区间覆盖,倍增法好题

Minimal Segment Cover
题意:给你n个区间,有m次询问,n,m<2e5,每次询问区间A最少需要几个区间就能被完全覆盖,若不存在则输出-1。
思路:这道题先要预处理一下,就很好做了,先预处理出以每个点为起点,只用一个区间能到达的最右端点。然后我们就会想知道,每个起点用2个区间,3个区间,n个区间向右最远能到哪。因为n最大为2e5,所以最多可以用2e5个区间,这个时候就要用上倍增的思想了,用dp[i][j]表示起点i用2^j个区间所能到达的最右端点,则dp[i][j]=dp[ dp[i][j-1] ][j-1]。

代码有些细节要注意:

for(ll i=1,l,r;i<=n;i++){
    cin>>l>>r;v[l]=max(v[l],r);maxx=max(maxx,r);
}
for(ll i=0;i<=maxx;i++){
    v[i]=max(v[i-1],v[i]);
    //因为v[i-1]也可能包含i这个点
    dp[i][0]=v[i];
}
for(ll j=1;j<=20;j++){
    for(ll i=0;i<=maxx;i++){
        dp[i][j]=dp[dp[i][j-1]][j-1];
    }
}
for(ll i=1,l,r;i<=m;i++){
    cin>>l>>r;
    ll ans=0;
    for(ll j=20;j>=0;j--){
        if(dp[l][j]<r){
    //跳到r的上个点,来保证区间数最少
            ans+=(1<<j);
            l=dp[l][j];
        }
    }
    if(v[l]>=r)  cout<<ans+1<<endl;
    else  cout<<-1<<endl;
}

树上与a,b距离相等的点有多少个

A and B and Lecture Rooms
题意:如题,有多次询问
思路:size[i]存下子树i的规模,求得lca点下层的两个点a,b,答案就为
size[lca]-size[a]-size[b],答案具体的加减法不是这样,这只是表达个大概意思。

a中一区间是否存在子序列为p的一个排列-倍增好题

Lynyrd Skynyrd
题意:a,p,q<2e5,a是数列a长度,p是排列p长度,q是询问次数。子序列是指a的按相对顺序排列的序列,不是子串。p的一个排列是指循环排列。
思路:懒得写了,看这篇吧,注意其中的倍增思想
题解
解释一下题解中第二个3,其前缀是1,1在a中是第二个位置。这样做是因为我们要贪心,找a中距离Ai最近的那个数组成排列。

树中a的k级子孙有多少个-二分好题

Blood Cousins
当然也可以树上启发式合并来做,但我不会,下次再学
思路:对于求某个点的k级子孙,可以把树的每个结点按dfs序编号,记录每种深度含有的编号,记录每个结点,其子树的头个编号L[i]和最后一个编号R[i]。那么求其k级子孙,将其对应深度d,那么在所有深度为d的结点中,因为其结点编号按升序排序,所以可以二分的找到L[i]和R[i]的位置,两个位置之间的数量就为其k级子孙个数。

ST表-GCD-二分好题

Pair of Numbers
题意:给一个长度为3e5的数列,记区间a为[l,r],在区间a内存在一个数,使得所有区间a内的数都能整除它,求最长的区间a,若存在多个,则按左端点顺序从低到高输出。
思路:
1.朴素简单做法区间a内所有数都能整除它,那它一定是这个区间内的最小数。对于区间内一个数a1,我们求得以其为最小值的整除区间的最大长度,[l,r],下次枚举,我们可以直接从a[r+1]开始枚举,因为对于a1到ar之间的数,他们的左端点最多为a1,右端点最多为ar,区间长度不会比a1的长。
2.二分-ST表复杂做法
这么做纯属是想用下ST表,把题目弄复杂了,我们建两个ST表,一个最小值的,一个gcd的,然后二分长度,对于每个长度,我们枚举n个数。所以复杂度是O(n*logn)的。

LCA-简单变化题

Tree Queries
题意:一棵树,然后m个询问,每个询问有k个点,然后判断是否有一条路,要求这k个点要么在这条路上,要么和这条路相距为1.
思路:找到深度最大的结点a,则其他结点的父节点肯定都在根节点到结点a的链上,判断其他结点的父节点是不是结点a的祖先即可。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Marhoosh

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值