HDU 5875 Function(單調棧+線上倍增法)
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;
}