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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值