倍增的奇妙用处
前言:
这里并不打算教你什么是倍增,只是倍增奇妙的一些应用QwQ
应用:
1.倍增求树上LCA:
题目概述:给定x,y,求从x到y最多运多少货物。
经典题目了,先跑一遍最大生成树,然后树上倍增处理出路上最小载重。秒了
2.矩阵快速幂:
题目概述:有N个点的有向图,数条有边权的有向边,求恰好在T时刻到达N点的方案数。(1<=边权<=9)
分析:如果没有边权就是裸题了,众所周知邻接矩阵的k次方就是k步到达某个点的方案。
边权不大,只要把每个点拆成9个点,分别代表边权长,每次目标点连到1号,出发点连到相应节点,再每个点的1到9再连上边就可以跑矩阵快速幂了。
矩阵满足结合律可以快速幂
3.倍增加速转移:
对于目的地距离明确,边界限制明确的移动,通常可以倍增加速转移(莽过去)。
题目概述:数轴上有n个点,有一只青蛙在上面跳,每次跳到第k小的点(距离相同向下标较小的点跳),求从每个点出发跳m次后在哪个点。
分析:显然只要处理出每个点下一次跳到哪里就可以开始倍增了qwq,那怎么处理下一次跳到哪里呢(?)
可以发现对于一个点,它的k小点一定分布在它的周围,而且随着点的移动分布范围单调移动,显然是一个尺取法的模型
int l=1,r=m+1;
f[1][0]=r;
for(int i=2;i<=n;++i)
{
while(r<n&&a[i]-a[l]>a[r+1]-a[i]) ++l,++r;
f[i][0]=(a[r]-a[i]>a[i]-a[l]?r:l);
}
然后每个点倍增暴跳就可以了
4.倍增解RMQ问题
倍增解RMQ包括熟知的ST表,这里放一个ST的扩展。
题目概述:在长度为 l 到 r 的所有区间中选出区间和最大的m个
分析:区间问题先维护一个前缀和总没错qwq
维护了前缀和我们发现,如果固定一个左端点s,那么右端点 t 的取值范围是固定的,区间和为 sum[ t ] - sum [ s-1 ] ;
当s固定之后,sum [ s-1 ] 就成为了定值,现在找区间和最大就变成了在某个区间内找sum [ t ]最大的问题 。
st表qwq;
每次选取一个区间之后,我们要把这个区间右端点的左侧和右侧重新利用起来,所以我们应该在找最大值的时候记录右端点的位置,对st表进行魔改。
inline int rmq(int l,int r)//查询
{
int maxans=-2e9,pos;
for(int d=19;d>=0;--d)
{
if(l+(1<<d)-1<=r)
{
if(mx[l][d]>maxans)
{
maxans=mx[l][d];
pos=id[l][d];
}
l=f[l][d]+1;
}
}
return pos;
}
for(int i=1;i<=19;++i)//预处理
{
for(int j=1;j<=n;++j)
{
f[j][i]=f[f[j][i-1]+1][i-1];
if(mx[j][i-1]>mx[f[j][i-1]+1][i-1])
{
mx[j][i]=mx[j][i-1];
id[j][i]=id[j][i-1];
}
else
{
mx[j][i]=mx[f[j][i-1]+1][i-1];
id[j][i]=id[f[j][i-1]+1][i-1];
}
}
}
那么怎么在众多区间中选最大值呢?当然是堆啦,每次把一个区间的信息扔进堆里
信息包括:区间左端点,右端点,右端点左右边界;
AC代码:
#include<bits/stdc++.h>
using namespace std;
inline int read()
{
int x=0,f=1;
char ch;
for(ch=getchar();(ch<'0'||ch>'9')&&ch!='-';ch=getchar());
if(ch=='-') f=0,ch=getchar();
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
return f?x:-x;
}
int n,m,l,r;
long long res;
int a[550010];
int sum[550010];
int mx[550010][21];
int id[550010][21];
int f[550010][21];
struct point
{
int s,l,r,t;
bool operator <(point y)const
{
return sum[t]-sum[s-1]<sum[y.t]-sum[y.s-1];
}
}miao,wu;
priority_queue<point> q;
inline int rmq(int l,int r)
{
int maxans=-2e9,pos;
for(int d=19;d>=0;--d)
{
if(l+(1<<d)-1<=r)
{
if(mx[l][d]>maxans)
{
maxans=mx[l][d];
pos=id[l][d];
}
l=f[l][d]+1;
}
}
return pos;
}
signed main()
{
n=read(),m=read(),l=read(),r=read();
for(int i=1;i<=n;++i)
{
a[i]=read();
sum[i]=sum[i-1]+a[i];
mx[i][0]=sum[i];
id[i][0]=i;
f[i][0]=i;
}
for(int i=1;i<=19;++i)
{
for(int j=1;j<=n;++j)
{
f[j][i]=f[f[j][i-1]+1][i-1];
if(mx[j][i-1]>mx[f[j][i-1]+1][i-1])
{
mx[j][i]=mx[j][i-1];
id[j][i]=id[j][i-1];
}
else
{
mx[j][i]=mx[f[j][i-1]+1][i-1];
id[j][i]=id[f[j][i-1]+1][i-1];
}
}
}
for(int i=1;i<=n;++i)
{
if(i+l-1>n) break;
miao.s=i;
miao.l=i+l-1;
miao.r=min(i+r-1,n);
miao.t=rmq(miao.l,miao.r);
q.push(miao);
}
while(m--)
{
miao=q.top();
q.pop();
res+=sum[miao.t]-sum[miao.s-1];
if(miao.t>miao.l)
{
wu.s=miao.s;
wu.l=miao.l;
wu.r=miao.t-1;
wu.t=rmq(wu.l,wu.r);
q.push(wu);
}
if(miao.t<miao.r)
{
wu.s=miao.s;
wu.l=miao.t+1;
wu.r=miao.r;
wu.t=rmq(wu.l,wu.r);
q.push(wu);
}
}
printf("%lld
",res);
return 0;
}
5.其他奇怪的暴搞方式
题目概述:给一个序列,求有多少区间满足区间或>区间最大值。
分析:我们可以转化为求有多少区间不满足区间或>区间最大值问题。
对于每一个数字,求出它的控制范围(在哪个区间内是最大值),这个可以用单调栈O(n)求出来。
每个控制范围被最大值分为左右两侧,我们可以对较小的一侧暴力遍历,对另一侧倍增地跳
复杂度?复杂度无法保证,大概是O(能过)
#include<bits/stdc++.h>
using namespace std;
#define int long long
inline int read()
{
int x=0,f=1;
char ch;
for(ch=getchar();(ch<'0'||ch>'9')&&ch!='-';ch=getchar());
if(ch=='-') f=0,ch=getchar();
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
return f?x:-x;
}
int n,res;
int a[500010],l[500010],r[500010];
int fl[500010][21];
int fr[500010][21];
int st[500010],top;
signed main()
{
n=read();
a[0]=a[n+1]=1e9+7;
for(int i=1;i<=n;++i)
{
a[i]=read();
fl[i][0]=fr[i][0]=a[i];
while(a[i]>=a[st[top]]&&top) --top;
l[i]=st[top]+1;
st[++top]=i;
}
top=0;
st[top]=n+1;
for(int i=n;i>=1;--i)
{
/*注意一侧开区间一侧闭区间*/
while(a[i]>a[st[top]]&&top) --top;
r[i]=st[top]-1;
st[++top]=i;
}
for(int i=1;i<=18;++i)
{
for(int j=1;j<=n;++j)
{
fl[j][i]=fl[j][i-1]|fl[j+(1<<(i-1))][i-1];
}
for(int j=n;j>=1;--j)
{
fr[j][i]=fr[j][i-1];
if(j>=(1<<(i-1)))
fr[j][i]=fr[j][i-1]|fr[j-(1<<(i-1))][i-1];
}
}
for(int i=1;i<=n;++i)
{
if(i-l[i]<=r[i]-i)
{
for(int k=l[i];k<=i;++k)
{
int now=k;
int sum=a[k];
for(int d=18;d>=0;--d)
{
if(now+(1<<d)<=r[i]&&(sum|fl[now+1][d])<=a[i])
{
sum |= fl[now+1][d];
now+=(1<<d);
}
}
if(now>=i) res+=(now-i+1);
}
}
else
{
for(int k=i;k<=r[i];++k)
{
int now=k;
int sum=a[k];
for(int d=18;d>=0;--d)
{
if(now-(1<<d)>=l[i]&&(sum|fr[now-1][d])<=a[i])
{
sum |= fr[now-1][d];
now-=(1<<d);
}
}
if(now<=i) res+=(i-now+1);
}
}
}
printf("%lld
",(n)*(n+1)/2-res);
return 0;
}