还没有去省里竞赛过~~这回怎么也得争到名额,加油~~一步步来,九月份去了~我只是菜鸟,还太多不懂,只能一个个来,前两天看了下LCA问题,和它与之紧密联系的是RMQ算法,今天无论如何得把这个弄懂了~~
RMQ算法全称为(Range Minimum/Maximum Query)意思是给你一个长度为n的数组A,求出给定区间的最值的下标。当然我们可以采用枚举,但是我们也可以使用线段树来优化,复杂度为(nlogn),但是最好的办法是采用Sparse_Table算法,简称ST算法。他能在进行(nlogn)的预处理后达到n(1)的效率。下面来分析下最大值和最小值,都要用到DP的思想。
最小值(Mininun):我们可以用F(i,j)表示区间[i,i+2^j-1]间的最小值。我们可以开辟数组来保存F(i,j)的值,例如:F(2,4)就是保存区间[2,2+2^4-1]=[2,17]的最小值。那么F(i,0)的值是确定的,就为i这个位置所指的元素值,这时我们可以把区间[i,i+2^j-1]平均分为两个区间,因为j>=1的时候该区间的长度始终为偶数,可以分为区间[i,i+2^(j-1)-1]和区间[i+2^(j-1)-1,i+2^j-1],即取两个长度为2^(j-1)的块取代和更新长度为2^j的块,那么最小值就是这两个区间的最小值的最小值,动态规划为:F[i,j]=min(F[i,j-1],F[i+2^(j-1),j-1]).同理:最大值就是F[i,j]=max(F[i,j-1],F[i+2^(j-1),j-1]).
现在求出了F[i,j]之后又是怎样求出最大值或者最小值的,怎么转换为o(1)这种算法的~这就是ST算法:
这个时候询问时只要取k=ln(j-i+1)/ln2即可,那么可以令A为i到2^k的块,和B为到2^k结束的长度为2^k的块;那么A,B都是区间[i,j]的子区间,所以即求A区间的最小值和B区间的最小值的最小值。这个时候动态规划为:RMQ(i,j)=min(F[i,k],F[j-2^k+1,k]);
下面是求区间最小值的模板:
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
using namespace std;
const int MAX=10010;
#define max(a,b) a>b?a:b
#define min(a,b) a<b?a:b
#define CLR(arr,val) memset(arr,val,sizeof(arr))
int n,m,low,high,a[MAX];
class RMQ{
public:
void rmq()
{ int temp=(int)(log((double)n)/log(2.0));
for(int i=0;i<n;i++)
DP[i][0]=a[i];
for(int j=1;j<=temp;j++)
for(int i=0;i<n;i++)
if(i+(1<<j)<n) DP[i][j]=min(DP[i][j-1],DP[i+(1<<(j-1))][j-1]);
}
int Minimum(int L,int H)
{ int k=(int)(log((double)H-L+1)/log(2.0));
return min(DP[L][k],DP[H-(1<<k)+1][k]);
}
void Init(){CLR(DP,0);}
private:
int DP[MAX][20];
};
int main()
{ RMQ R;
R.Init();
scanf("%d%d",&n,&m);
for(int i=0;i<n;i++)
scanf("%d",&a[i]);
R.rmq();
while(m--)
{ scanf("%d%d",&low,&high);
printf("%d\n",R.Minimum(low,high));
}
return 0;
}
当然对于RMQ并不只有这个用法,我们可以用它来解决LCA问题。
假设LCA(T,u,v)表示在有根树T中,询问一个离根最远的结点x,使得x为u,v的公共祖先。现在分析下LCA向RMQ问题转化的过程:
对有根树T进行深度优先遍历(DFS),将遍历到的结点按照顺序记录下来,那么我们会得到一个长度为2N-1的序列,称之为T的欧拉序列F,设序列Depth是DFS遍历过程中的结点深度的变化情况。其中每一个结点都会出现在欧拉序列F中,我们记录结点u在欧拉序列中出现的第一个位置pos(u);根据DFS的特性,对于任意两个结点u,v,那么从pos(u)(也就是第一次访问u的时候)到pos(v)(第一次访问v)的过程中,所经历的路径为F(pos(u).....pos(v)),虽然这些包括u的后代,但是其深度最小的结点一定是u和v的LCA(公共祖先),不论pos(u)与pos(v)的关系如何,都一定有LCA(T,u,v)=RMQ(Depth,pos(u),pos(v));
下面这个图是有根树的欧拉序列F和深度序列B已经pos(u)的变化情况:
1 深度为0
/ \ \
2 3 4 深度为1
/ \
5 6 深度为2
那么欧拉序列F:1 2 5 2 6 2 1 3 1 4 1;深度序列Depth为: 0 1 2 1 2 1 0 1 0 1 0,pos(u)为:1 2 8 10 3 5
还是以poj 1330为例:将LCA转化为RMQ求解,见代码:
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<vector>
using namespace std;
const int MAX=20001;//定义10001RE了n次,定义太小了,以后RE后可以适当的把数组开大点
#define CLR(arr,val) memset(arr,val,sizeof(arr))
int n,total,Depth[2*MAX],pos[MAX],F[2*MAX],Indegree[MAX];
vector<int> Adj[MAX];
bool visited[MAX];
void Init()
{ total=0;
CLR(pos,0);
CLR(Indegree,0);
CLR(visited,false);
for(int i=0;i<MAX;i++) Adj[i].clear();
}
void DFS(int u,int cur)//cur记录深度
{ if(!visited[u])//说明该节点未被访问过,那么记录第一次出现的位置
{ visited[u]=true;
pos[u]=total;
}
Depth[total]=cur;//记录深度序列
F[total++]=u;//记录欧拉序列
for(vector<int>::size_type i=0;i<Adj[u].size();i++)
{ DFS(Adj[u][i],cur+1);
Depth[total]=cur;
F[total++]=u;
}
}
class RMQ{
public:
void clear(){CLR(DP,0);}
void rmq()
{ int temp=(int)(log((double)total)/log(2.0));
for(int i=0;i<total;i++)
DP[i][0]=i;
for(int j=1;j<=temp;++j)
for(int i=0;i<total;++i)
DP[i][j]=Depth[DP[i][j-1]]<Depth[DP[i+(1<<(j-1))][j-1]]?DP[i][j-1]:DP[i+(1<<(j-1))][j-1];
}
int Minimum(int L,int H)
{ int k=(int)(log((double)H-L+1)/log(2.0));
return Depth[DP[L][k]]<Depth[DP[H-(1<<k)+1][k]]?DP[L][k]:DP[H-(1<<k)+1][k];
}
private:
int DP[2*MAX][20];
}R;
int LCA(int u,int v)
{ return u<=v?R.Minimum(u,v):R.Minimum(v,u);
}
int main()
{ int k,u,v;
scanf("%d",&k);
while(k--)
{ scanf("%d",&n);
R.clear();
Init();
for(int i=0;i<n-1;i++)
{ scanf("%d%d",&u,&v);
Adj[u].push_back(v);
Indegree[v]++;
}
for(int i=1;i<=n;i++)//从根结点开始DFS
if(!Indegree[i]) {DFS(i,0);break;}
R.rmq();
scanf("%d%d",&u,&v);
printf("%d\n",F[LCA(pos[u],pos[v])]);
}
return 0;
}
例题2:NYOJ 119(士兵杀敌),一看就知道是RMQ算法,但是以前不会做,随便粘贴了别人的代码,过了,今天才自己做下,人总是再进步,谁都有过粘贴别人的代码过题的时候,但是你也在进步,重要的是坚持,RLE了n次,少在rmq()中加了if语句。
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
using namespace std;
const int MAX=100010;
#define max(a,b) a>b?a:b
#define min(a,b) a<b?a:b
#define CLR(arr,val) memset(arr,val,sizeof(arr))
int n,m,low,high,Kill[MAX];
class RMQ{
public:
void rmq()
{ int temp=(int)(log((double)n)/log(2.0));
for(int i=0;i<n;i++)
Min_DP[i][0]=Max_DP[i][0]=Kill[i];
for(int j=1;j<=temp;j++)
for(int i=0;i<n;i++)
if(i+(1<<(j-1))<n)
{ Min_DP[i][j]=min(Min_DP[i][j-1],Min_DP[i+(1<<(j-1))][j-1]);
Max_DP[i][j]=max(Max_DP[i][j-1],Max_DP[i+(1<<(j-1))][j-1]);
}
}
int Result(int L,int H)
{ int k=(int)(log((double)H-L+1)/log(2.0));
int Min=min(Min_DP[L][k],Min_DP[H-(1<<k)+1][k]);
int Max=max(Max_DP[L][k],Max_DP[H-(1<<k)+1][k]);
return Max-Min;
}
void Init()
{ CLR(Min_DP,0);
CLR(Max_DP,0);
}
private:
int Min_DP[MAX][20],Max_DP[MAX][20];
}R;
int main()
{ scanf("%d%d",&n,&m);
for(int i=0;i<n;i++)
scanf("%d",&Kill[i]);
R.Init();
R.rmq();
while(m--)
{ scanf("%d%d",&low,&high);
printf("%d\n",R.Result(low-1,high-1));
}
return 0;
}