2016 ACM/ICPC Dalian Online-1008 Function

本文介绍了一种针对区间取模运算的高效查询算法。通过预处理和数据结构优化,实现对大量区间取模问题的有效解决。算法利用优先队列和线段树等数据结构,实现了O(nlog^2n)的时间复杂度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


题意:给定长度为n的数组A和Q个询问。询问[L,R],求A(L)%A(L+1)%A(L+2)%……%A(R) 。其中  1≤N,Q≤100000

一个性质:大数对小数取余,结果小于大数的一半。所以每个大数最多对小数取log(ai)次

题解一:转自点击打开链接

已知mod操作类似gcd操作,结果是单调的,只会小不会大。

把所有询问预存,按左边界排序,当前左边界存在于询问时,加入优先队列,对于当前位置,优先队列中大于a[i]的都对a[i]取余,取到 < a[i]即可停止,更小的肯定更无变化,根据右边界抛出即可

复杂度为O(nlgnlgn)

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#include <queue>

using namespace std;

const int N=1e5+10;

struct Node{
    int R,v,n;
    Node(){}
    Node(int R,int v,int n):R(R),v(v),n(n){}
    bool operator <(const Node&a)const{
        return v<a.v;
    }
};
struct Sec{
    int L,R,n;
    Sec(){}
    Sec(int L,int R,int n):L(L),R(R),n(n){}
}c[N];

int a[N],n,m;
int Ans[N];
priority_queue<Node>Q;
bool cmp(Sec i,Sec j){return i.L<j.L;}
void work()
{
    scanf("%d",&n);
    for (int i=1;i<=n;i++)scanf("%d",&a[i]);
    scanf("%d",&m);
    for (int i=1;i<=m;i++)scanf("%d%d",&c[i].L,&c[i].R),c[i].n=i;

    sort(c+1,c+m+1,cmp);

    int cNow=1;
    for (int i=1;i<=n;i++){
        while (!Q.empty()){
            Node tmp=Q.top();
            if (i>tmp.R){Ans[tmp.n]=tmp.v;Q.pop();}else
            if (tmp.v>=a[i]){//a[i]==0
                Q.pop();
                Q.push(Node(tmp.R,tmp.v%a[i],tmp.n));
            }else break;
        }
        while (cNow<=m && c[cNow].L==i){
            Q.push(Node(c[cNow].R,a[i],c[cNow].n));
            cNow++;
        }
    }
    while (!Q.empty()){
        Node tmp=Q.top();Q.pop();
        Ans[tmp.n]=tmp.v;

    }
    for (int i=1;i<=m;i++)printf("%d\n",Ans[i]);
}
int main()
{
    //freopen("1.txt","r",stdin);
    int Case;scanf("%d",&Case);
    while (Case--)work();
    return 0;
}


题解二:对于询问i,每次寻找值小于等于当前值的最近位置pos,不断取模,直到pos>R或者找不到这样的值为止。

      对于寻找,用线段树,区间[L,R]表示a(i)的值为[L,R]时的最小位置。由于可能位置在L之前,所以我们可以对询问按左边界L从小到大排序。枚举位置(i-1)->i时,除去a(i)在线段树中的值,可以用下一个值为a(i)的点覆盖。

处理时需要先对A离散化。复杂度为O(nlgnlgn)

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#include <queue>

using namespace std;

const int N=1e5+10;

struct Sec{
    int L,R,n;
    Sec(){}
    Sec(int L,int R,int n):L(L),R(R),n(n){}
}c[N];

int a[N],n,m;
int d[N],dn;
int T[N<<2];
int Ans[N];
int nxt[N],Head[N];
bool cmp(Sec i,Sec j){return i.L<j.L;}
void WZJ()
{
    for (int i=1;i<=n;i++)d[i]=a[i];
    sort(d+1,d+n+1);
    dn=unique(d+1,d+n+1)-(d+1);
    for (int i=1;i<=n;i++)
        a[i]=lower_bound(d+1,d+dn+1,a[i])-d;
}
void Insert(int p,int L,int R,int pos,int v)
{
    if (L==R){T[p]=v;return ;}
    int mid=(L+R)>>1;
    if (pos<=mid)Insert(p<<1,L,mid,pos,v);
    else Insert(p+p+1,mid+1,R,pos,v);
    T[p]=min(T[p+p],T[p+p+1]);
}
int Find(int p,int L,int R,int v)
{
    if (R<=v)return T[p];
    int mid=(L+R)>>1;
    if (v<=mid)return Find(p+p,L,mid,v);
    else return min(T[p+p],Find(p+p+1,mid+1,R,v));
}
void Look(int p,int L,int R)
{
    printf("%d %d %d\n",L,R,T[p]);
    if (L==R)return ;
    int mid=(L+R)>>1;
    Look(p+p,L,mid);
    Look(p+p+1,mid+1,R);
}
void work()
{
    scanf("%d",&n);
    for (int i=1;i<=n;i++)scanf("%d",&a[i]);
    WZJ();

    memset(Head,0,sizeof Head);
    for (int i=n;i>=1;i--){
        nxt[i]=Head[a[i]];
        Head[a[i]]=i;
    }

    for (int i=1;i<(n<<2);i++)T[i]=dn+1;
    for (int i=1;i<=dn;i++)
        Insert(1,1,dn,a[Head[i]],Head[i]);

    scanf("%d",&m);
    for (int i=1;i<=m;i++)scanf("%d%d",&c[i].L,&c[i].R),c[i].n=i;
    sort(c+1,c+m+1,cmp);

    int cNow=1;
    for (int i=1;i<=n;i++){
        if (nxt[i]==0)Insert(1,1,dn,a[i],n+1);
        else Insert(1,1,dn,a[i],nxt[i]);

        for (;cNow<=m && c[cNow].L==i;cNow++){
            int v=d[a[i]],R=c[cNow].R;
            while (1){
                int tmp=upper_bound(d+1,d+dn+1,v)-d-1;
                if (tmp==0) break;
                int pos=Find(1,1,dn,tmp);
                if (pos>R)break;
                v=v%d[a[pos]];
            }
            Ans[c[cNow].n]=v;
        }
    }

    for (int i=1;i<=m;i++)printf("%d\n",Ans[i]);
}
int main()
{
    //freopen("1.txt","r",stdin);
    int Case;scanf("%d",&Case);
    while (Case--)work();
    return 0;
}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值