RMQ模板请看 :https://blog.csdn.net/qq_43333395/article/details/89442824
首先来一道航电例题(hdu 5726)
题目链接:
http://acm.hdu.edu.cn/showproblem.php?pid=5726
题意: 给一个数组a,大小为n,接下来有m个询问,每次询问给出l、r,定义f[l,r]=gcd(al,al+1,…,ar),问f[l,r]的值 和 有多少对(l’,r’)使得f[l’,r’]=f[l,r]。n<=10万,m<=10万,1<=l<=r<=n,1<=l’<=r’。
思路:
第一步比较简单,预处理一下,定义f[i][j]为:ai开始,连续2^j个数的最大公约数,所以f[1][0]=a[1],f[1][1]=gcd(a1,a2),f[1][2]=gcd(a1,a2,a3,a4)。其实就是动态规划,让i从1-n,让j从0-17,递推上去即可。
递推公式如下:
1. f[i][0]=a[i];
2. f[i][j]=gcd(f[i][j-1],f[i+(1<<j-1)][j-1]);
就如同f[1][2]=gcd(f[1][1],f[3][1])=gcd(gcd(f[1][0],f[2][0]),gcd(f[3][0],f[4][0]));
通过上述预处理,查询时就只需O(logn)时间,如下:
令k=log2(r-l+1),look(l,r)=gcd(f[l][k],f[r-(1<<k)+1][k]);
注:f[l][k] 和 f[r-(1<<k)+1][k]可能会有重叠,但不影响最终的gcd值。
比赛时第二步没想出来,太可惜了。。。
第二步,我们可以枚举左端点 i 从1-n,对每个i,二分右端点,计算每种gcd值的数量,因为如果左端点固定,gcd值随着右端点的往右,呈现单调不增,而且gcd值每次变化,至少除以2,所以gcd的数量为nlog2(n)种,可以开map<int,long long>存每种gcd值的数量,注意n大小为10万,所以数量有可能爆int。
#include<bits/stdc++.h>
using namespace std;
#define LL long long
int dp[100010][32];
int a[100010];
int n,m,g,j;
int gcd(int a,int b){
return b?gcd(b,a%b):a;
}
void rmq_init(){
for(int j=1;j<=n;j++) dp[j][0]=a[j];
for(int i=1;(1<<i)<=n;i++){
for(int j=1;j+(1<<i)-1<=n;j++){
dp[j][i]=gcd(dp[j][i-1],dp[j+(1<<(i-1))][i-1]);
}
}
}
int rmq_qui(int l,int r){
int k=(int)log2(double(r-l+1));
return gcd(dp[l][k],dp[r-(1<<k)+1][k]);
}
map<int,long long>mp;
void setTable(){
mp.clear();
for(int i=1;i<=n;i++){
g=dp[i][0],j=i;
while(j<=n){
int l=j,r=n;
while(l<r){
int mid=(l+r+1)>>1;
if(rmq_qui(i,mid)==g) l=mid;
else r=mid-1;
}
mp[g]+=l-j+1; //相当于二分枚举相加
j=l+1;
g=rmq_qui(i,j);
}
}
}
int main(){
int t,l,r;
int cas=1;
scanf("%d",&t);
while(t--){
printf("Case #%d:\n",cas++);
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
rmq_init();
setTable();
scanf("%d",&m);
for(int i=0;i<m;i++){
scanf("%d %d",&l,&r);
int g=rmq_qui(l,r);
printf("%d %lld\n",g,mp[g]);
}
}
}
再来一道华工校赛的题目:https://ac.nowcoder.com/acm/contest/625/H
题目大意:
给你n个数,让你求所有l到r的gcd之和,答案mod1000000007 (l,r∈(1,n))
是RMQ进行 O(n log n log 109)的预处理搞出区间GCD值,然后每次就可以 O(log109)回答任意区间的GCD值。之后为了计算所有区间的GCD值之和,我们考虑O(n) 枚举区间左端点,然后每次右端点从左往右二分+RMQ找到第一个会让区间GCD变化的位置,并累加答案。注意到右端点从左往右扫的过程中,GCD至多只会变化O(log109) 次,所以这样做总复杂度O(n log n log2109)就变成了 。
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
int dp[500010][20];
int a[500010];
int len[500010];
int n,m,g,j;
const LL mod=1e9+7;
int gcd(int a,int b){
return b?gcd(b,a%b):a;
}
void rmq_init(){
for(int j=1;j<=n;j++) dp[j][0]=a[j];
for(int i=1;(1<<i)<=n;i++){
for(int j=1;j+(1<<i)-1<=n;j++){
dp[j][i]=gcd(dp[j][i-1],dp[j+(1<<(i-1))][i-1]);
}
}
for(int i=1;i<=n;i++) { //存len数组可以减少运算的时间,很重要,不然会tle
len[i]=len[i-1];
if((1<<(len[i]+1))<i) len[i]++;
}
}
int rmq_qui(int l,int r){
int k=len[r-l+1];
return gcd(dp[l][k],dp[r-(1<<k)+1][k]);
}
map<int,long long>mp;
void setTable(){
LL ans=0;
mp.clear();
for(int i=1;i<=n;i++){
g=dp[i][0],j=i;
while(j<=n){
int l=j,r=n;
while(l<r){
int mid=(l+r+1)>>1;
if(rmq_qui(i,mid)==g) l=mid;
else r=mid-1;
}
ans=(ans+LL(l-j+1)*g)%mod;
j=l+1;
g=rmq_qui(i,j);
}
}
printf("%lld\n",ans);
}
int main(){
int t,l,r;
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
rmq_init();
setTable();
}