可重复贡献问题(如rmq问题):
是指对于运算opt满足x opt x=x,则对应的区间询问就是一个可重复贡献问题。rmq涉及的运算就是max以及min,同时,对于gcd运算(最大公约数)、按位与、按位或等运算,rmq都能实现对应的静态区间查询。
基于ST倍增的rmq解决算法
ST表构造思路:
-
使用二维数组,M[i][j]表示以i开始,长度为$$2^j$$区间的最值。
-
使用dp状态转移求解st表,相当于区间最值相当于两个子区间和st值与的st值的最值,分解为子问题求解。
代码实现:
void ST(void)
{
for(int i=1;i<=n;i++)
st[i][0]=a[i];
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]);
}
}
查询操作:
若要查询区间[i,j]的最值,设,可证明一定大于等于该区间长度的一半,则
代码实现:
ll query(int l,int r)
{
int len=r-l+1,k;
for(k=0;(1<<k)<=len;k++);
k--;
return max(st[l][k],st[r-(1<<k)+1][k]);
}
完整实现:(以【模板】ST 表 && RMQ 问题 - 洛谷为例)
#include<bits/stdc++.h>
#define maxn 1000
#define INF 0x3f3f3f3f
#define ll long long
using namespace std;
ll n,m,a[maxn];
ll st[maxn][maxn];
void ST(void)
{
for(int i=1;i<=n;i++)
st[i][0]=a[i];
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(int l,int r)
{
int len=r-l+1,k;
for(k=0;(1<<k)<=len;k++);
k--;
return max(st[l][k],st[r-(1<<k)+1][k]);
}
int main()
{
scanf("%lld%lld",&n,&m);
for(int i=1;i<=n;i++)
scanf("%lld",&a[i]);
ST();
for(int i=1;i<=m;i++)
{
int x,y;
scanf("%d%d",&x,&y);
printf("%lld\n",query(x,y));
}
system("pause");
}
基于ST表的LCA解决思路
-
算法思路:
-
首先,若两点u,v深度不同,将深度更深的点通过循环操作跳至与另一个点深度相同,判断是否为同一个点,若是直接输出答案,若非,进入下一步。
-
根据深度选择向上跳跃的步长,每次选择,i在每次循环后递减,目的是找出两者的最近公共祖先,若第一次就发现两点跳到了同一点,则不着急跳,进行下一轮循环,若下一轮循环两点不在同一点,则往上跳,因为此时说明,lca一定是树结构上上一轮循环观测的点与此时跳跃的点之间的点,此时不断跃进直到走到lca的子节点处停下;
-
输出father[u]或father[v]
-
代码实现:
预处理:
void Pre(void)
{
for(int i=0;i<=n;i++)
for(int j=0;(1<<j)<=n;j++)//一个节点可能有最多n个祖先
acs[i][j]=-1;
for(int i=1;i<=n;i++)
acs[i][0]=f[i];
for(int j=1;(1<<j)<=n;j++)
for(int i=1;i<=n;i++)
if(acs[i][j-1]!=-1)
acs[i][j]=acs[acs[i][j-1]][j-1];
}
LCA:
int LCA(int u,int v)
{
int tem,lg;
if(d[u]<d[v])
{
tem=u;
u=v;
v=tem;
}
for(lg=0;(1<<lg)<=d[u];lg++);
lg--;
for(int i=lg;i>=0;i--)
if((1<<i)<=d[u]-d[v])u=acs[u][i];//跳至两点深度相同
if(u==v) return u;
for(int i=lg;i>=0;i--)
if(acs[u][i]!=-1&&acs[u][i]!=acs[v][i])//继续跳至lca的子节点处
u=acs[u][i],v=acs[v][i];
return f[u];
}
完整实现:
#include<bits/stdc++.h>
#define ll long long
#define maxn 1000
#define INF 0x3f3f3f3f
using namespace std;
int n,m,s;
int head[maxn];
struct Edge{
int to;
int next;
}edge[maxn];
int tot=0,d[maxn],f[maxn],acs[maxn][maxn];
void addedge(int u,int v)
{
edge[++tot].to=v;
edge[tot].next=head[u];
head[u]=tot;
}
void dfs(int x,int fa)
{
f[x]=fa;
d[x]=d[fa]+1;
for(int i=head[x];~i;i=edge[i].next)
{
int to=edge[i].to;
if(to==fa)continue;
dfs(to,x);
}
}
void Pre(void)
{
for(int i=0;i<=n;i++)
for(int j=0;(1<<j)<=n;j++)//一个节点可能有最多n个祖先
acs[i][j]=-1;
for(int i=1;i<=n;i++)
acs[i][0]=f[i];
for(int j=1;(1<<j)<=n;j++)
for(int i=1;i<=n;i++)
if(acs[i][j-1]!=-1)
acs[i][j]=acs[acs[i][j-1]][j-1];
}
int LCA(int u,int v)
{
int tem,lg;
if(d[u]<d[v])
{
tem=u;
u=v;
v=tem;
}
for(lg=0;(1<<lg)<=d[u];lg++);
lg--;
for(int i=lg;i>=0;i--)
if((1<<i)<=d[u]-d[v])u=acs[u][i];
if(u==v) return u;
for(int i=lg;i>=0;i--)
if(acs[u][i]!=-1&&acs[u][i]!=acs[v][i])
u=acs[u][i],v=acs[v][i];
return f[u];
}
int main()
{
scanf("%d%d%d",&n,&m,&s);
for(int i=0;i<=n;i++)
head[i]=-1;
for(int i=1;i<=n-1;i++)
{
int u,v;
scanf("%d%d",&u,&v);
addedge(u,v);
addedge(v,u);//链式前向星无向边加两次
}
dfs(s,s);
Pre();
for(int i=1;i<=m;i++)
{
int x,y;
scanf("%d%d",&x,&y);
printf("%d\n",LCA(x,y));
}
system("pause");
}