Description
一个长度为n的序列Ai,m次查询,每次查询求f(l,r),其定义如下:
Input
第一行一整数T表示用例组数,每组用例首先输入一整数n表示序列长度,之后n个整数Ai表示该序列,然后输入一整数m表示查询数,最后m行每行两个整数l,r表示一次查询(1<=n<=1e5,1<=Ai<=1e9,1<=l<=r<=n)
Output
对于每次查询,输出f(l,r)的值
Sample Input
1
3
2 3 3
1
1 3
Sample Output
2
Solution
首先说一个模运算性质:如果y<=x,那么x%y < x/2. 证明如下:
若y<=x/2,x%y < y<=x/2,结论成立;
若x/2 < y<=x,x%y<=x-y < x/2,结论成立.
f(l,r)本质上就是求A[l]连续模A[l+1],…,A[r]的结果,考虑到模一个较小数之后再模大数没有意义,如果对于每个l我们能够处理出以l起始的一个不增序列来让A[l]模的话,那么根据上面提到的模运算性质,这个序列长度至多log A[l](模log A[l]个数后答案是0就不需要继续模了),也就是说,如果能够处理出这不增序列,每次二分查找第一个不大于当前结果的模数,至多log A[l]次二分就可以得到f(l,r)的值,下面来解决如何处理出这些序列
如果对于每个起点i我们都把这个序列存起来显然内存太大,但是如果我们反过来看这个问题,对于每个数,它前面第一个不比它小的数是唯一的,如果把这种关系看作一条边的话,关系图就变成了一个森林,如果在第n+1个点放一个-1的话就变成了一棵树(A[i]>=1),树上任一节点i到根节点的简单路径就是我们需要的以i开始的不增序列,所以问题变成如果求一个数前面第一个不比它小的(即一个数后面第一个不比它大的)以及如果在树上路径二分查找
第一个问题可以用单调栈解决,第二个问题用在线倍增,每次跳到第一个小于等于当前结果的祖先节点的复杂度是O(logn),最多跳log A次,故总复杂度O(mlognlogA)
Code
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;
typedef long long ll;
#define maxn 111111
#define M 18
vector<int>g[maxn];
int fa[maxn][M];
int val[maxn];
void add(int u,int v)
{
g[v].push_back(u);
}
int r[maxn],sta[maxn],vis[maxn];
void monotonic_stack(int *a,int n)
{
for(int i=1;i<=n;i++)r[i]=i;
int p=0;
for(int i=1;i<=n;i++)
{
if(!p||a[i]>=a[sta[p]]) sta[++p]=i;
else
{
while(p&&a[i]<a[sta[p]])
r[sta[p]]=i,p--;
sta[++p]=i;
}
}
while(p)r[sta[p]]=n,p--;
}
int p[maxn][M];
void dfs(int u,int fa)
{
for(int i=1;i<M;i++)p[u][i]=p[p[u][i-1]][i-1];
for(int i=0;i<g[u].size();i++)
{
int v=g[u][i];
if(v!=fa)
{
p[v][0]=u;
dfs(v,u);
}
}
}
void init(int root)
{
p[root][0]=root;
dfs(root,root);
}
int main(){
int T,n,m,ll,rr;
scanf("%d",&T);
while(T--)
{
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",&val[i]);
val[n+1]=-1;
for(int i=1;i<=n+1;i++)g[i].clear();
monotonic_stack(val,n+1);
memset(vis,0,sizeof(vis));
for(int i=1;i<=n+1;i++)
{
if(!vis[i])
{
vis[i]=1;
int j=r[i];
if(i==j)continue;
add(i,j);
while(!vis[j]&&j<=n)
add(j,r[j]),vis[j]=1,j=r[j];
}
}
init(n+1);
scanf("%d",&m);
while(m--)
{
scanf("%d%d",&ll,&rr);
int ans=val[ll],pos=ll;
while(1)
{
while(1)
{
int i;
for(i=M-1;i>=0;i--)
if(val[p[pos][i]]>ans)
break;
if(i!=-1)pos=p[p[pos][i]][0];
else pos=p[pos][0];
if(pos>rr)break;
if(val[pos]<=ans)
{
ans%=val[pos];
break;
}
}
if(pos>rr)break;
}
printf("%d\n",ans);
}
}
return 0;
}