• CH 4401/Luogu 4168


    题目链接:传送门

    题目链接:https://www.luogu.org/problemnew/show/P4168

    题解:

    经典的在线求区间众数的问题,由于区间众数不满足区间可加性,所以考虑分块,假设分块长度为 $S$,则总共分成 $T=N/S$ 块,

    对于每个询问 $[l,r]$,设点 $l$ 在第 $p$ 块,点 $r$ 在第 $q$ 块,假设第 $p+1$ 到第 $q-1$ 块这整一个区间为 $[L,R]$,

    那么,查询的区间就被分为 $[l,L)$ 和 $[L,R]$ 和 $(R,r]$ 三大块,显然可以分两种情况讨论:

    1、$[L,R]$ 这个区间的众数就是 $[l,r]$ 的众数;

    2、$[L,R]$ 这个区间的众数不是 $[l,r]$ 的众数,那么必然是由于 $[L,R]$ 区间内的某个数,它出现的次数,加上了 $[l,L)$ 和 $(R,r]$ 中出现的次数,超过了原本 $[L,R]$ 的众数;因此,这个必然在 $[l,L)$ 和 $(R,r]$ 中出现。

    这样一来,就很好算了,不妨对于所有可行的 $[L,R]$,预处理出一个数组 $cnt_{L,R}$,记录区间 $[L,R]$ 内每个数字出现的次数,同时再记录 $[L,R]$ 的众数是哪个,

    显然经过离散化处理后,$cnt_{L,R}$ 的空间复杂度为 $O(N)$,而所有可行的区间 $[L,R]$ 有 $O(T^2) = O(N^2 /S^2)$ 个;

    那么,对于每次查询 $[l,r]$,$O(S)$ 枚举 $[l,L)$ 和 $(R,r]$ 中的出现的数,把它们加到 $[L,R]$ 对应的 $cnt_{L,R}$ 数组之中,维护最大值的同时与 $[L,R]$ 的众数的出现次数进行比较,就可以找到众数,

    最后,再 $O(S)$ 地枚举 $[l,L)$ 和 $(R,r]$ 中的出现的数,把 $cnt_{L,R}$ 数组复原即可。

    因此,对于每次查询,$O(S)$ 即可求得答案,总的就是 $O(MS)$;而预处理是 $O(NT^2) = O(N^3 /S^2)$;所以总时间复杂度为 $O(MS + N^3 /S^2)$,可知,当 $S = sqrt[3]{{N^3 /M}} = frac{N}{{sqrt[3]{M}}}$ 时最小,为 $O(NM^{frac{2}{3}})$。

    AC代码:

    #include<bits/stdc++.h>
    using namespace std;
    typedef pair<int,int> pii;
    
    const int maxn=4e4+10;
    const int maxm=5e4+10;
    
    int n,m;
    int a[maxn],aid[maxn];
    int cnt[40][40][maxn];
    pii mode[40][40];
    
    int block[maxn],len,tot;
    int L[maxn],R[maxn];
    
    vector<int> v;
    inline int getid(int x){return lower_bound(v.begin(),v.end(),x)-v.begin()+1;}
    
    void update(int x,int y,int i,pii &res)
    {
        cnt[x][y][aid[i]]++;
        if(cnt[x][y][aid[i]]>res.first || (cnt[x][y][aid[i]]==res.first && a[i]<a[res.second]))
        {
            res.first=cnt[x][y][aid[i]];
            res.second=i;
        }
    }
    int ask(int l,int r)
    {
        int st=block[l],ed=block[r];
        pii res;
        if(ed-st<=1)
        {
            res=make_pair(0,0);
            for(int i=l;i<=r;i++) update(0,0,i,res);
            for(int i=l;i<=r;i++) cnt[0][0][aid[i]]--;
        }
        else
        {
            res=mode[st+1][ed-1];
            for(int i=l;i<=R[st];i++) update(st+1,ed-1,i,res);
            for(int i=L[ed];i<=r;i++) update(st+1,ed-1,i,res);
            for(int i=l;i<=R[st];i++) cnt[st+1][ed-1][aid[i]]--;
            for(int i=L[ed];i<=r;i++) cnt[st+1][ed-1][aid[i]]--;
        }
        return res.second;
    }
    
    int main()
    {
        cin>>n>>m;
        len=max(1,(int)(n/pow(m,1.0/3.0)));
        for(int i=1;i<=n;i++)
        {
            cin>>a[i];
            v.push_back(a[i]);
    
            block[i]=(i-1)/len+1;
            if(i==1) L[block[i]]=i;
            if(i==n) R[tot=block[i]]=i;
            if(2<=i && i<=n && block[i-1]!=block[i])
            {
                R[block[i-1]]=i-1;
                L[block[i]]=i;
            }
        }
        sort(v.begin(),v.end());
        v.erase(unique(v.begin(),v.end()),v.end());
        for(int i=1;i<=n;i++) aid[i]=getid(a[i]);
    
        memset(cnt,0,sizeof(cnt));
        for(int i=1;i<=tot;i++)
        {
            for(int j=i;j<=tot;j++)
            {
                mode[i][j]=make_pair(0,0);
                for(int k=L[i];k<=R[j];k++)
                {
                    cnt[i][j][aid[k]]++;
                    if(cnt[i][j][aid[k]]>mode[i][j].first || (cnt[i][j][aid[k]]==mode[i][j].first && a[k]<a[mode[i][j].second]))
                    {
                        mode[i][j].first=cnt[i][j][aid[k]];
                        mode[i][j].second=k;
                    }
                }
            }
        }
    
        int ans=0;
        while(m--)
        {
            int l,r;
            scanf("%d%d",&l,&r);
            l=(l+ans-1)%n+1;
            r=(r+ans-1)%n+1;
            if(l>r) swap(l,r);
            printf("%d
    ",ans=a[ask(l,r)]);
        }
    }

    (被这题僵了好久……深深感到自己码力的弱小……)

    题解2:

    依然同上面一样,对于每个询问 $[l,r]$,设点 $l$ 在第 $p$ 块,点 $r$ 在第 $q$ 块,假设第 $p+1$ 到第 $q-1$ 块这整一个区间为 $[L,R]$,查询的区间就被分为 $[l,L)$ 和 $[L,R]$ 和 $(R,r]$ 三大块,

    然后,这次不再建立数组 $cnt_{L,R}$,单纯记录区间 $[L,R]$ 的众数,

    但是,我们与上面的大体思路是一样的,我们依然要暴力枚举 $[l,L)$ 和 $(R,r]$ 里所有出现的数,看看这些数在 $[l,r]$ 区间里出现的次数能不能超过区间 $[L,R]$ 的众数,

    这样一来,我们考虑运用二分的方法:

    首先用一个邻接表,其中每个链表存储的是表头数字在 $a[1:N]$ 的所有出现位置,

    而后,我们对于 $[l,L)$ 和 $(R,r]$ 出现的任意一个数字,都能用两次二分查找到 $[l,r]$ 中的第一次出现位置和最后一次出现位置,两个位置相减即得 $[l,r]$ 内该数字出现了几次,

    最后,就是同上面一样的,去和 $[L,R]$ 的区间众数比较,尝试更新区间众数。

    那么,时间复杂度:预处理是 $O(NT) = O(N^2 / S)$;对于每次查询,$O(S log N)$ 即可求得答案,总的就是 $O(frac{{N^2 }}{S} + MSlog N)$,令 $S = frac{N}{{sqrt {Mlog N} }}$,使其最小为 $O(Nsqrt {Mlog N} )$。

    AC代码2:

    #include<bits/stdc++.h>
    using namespace std;
    typedef pair<int,int> pii;
    
    const int maxn=4e4+10;
    const int maxm=5e4+10;
    
    int n,m;
    int a[maxn],aid[maxn];
    vector<int> num[maxn];
    int cnt[maxn];
    pii mode[900][900];
    
    int block[maxn],len,tot;
    int L[maxn],R[maxn];
    
    vector<int> v;
    inline int getid(int x){return lower_bound(v.begin(),v.end(),x)-v.begin()+1;}
    
    void update(int i,int l,int r,pii &res)
    {
        int x=lower_bound(num[aid[i]].begin(),num[aid[i]].end(),l)-num[aid[i]].begin();
        int y=upper_bound(num[aid[i]].begin(),num[aid[i]].end(),r)-num[aid[i]].begin();
        if(y-x>res.first || (y-x==res.first && a[i]<a[res.second]))
        {
            res.first=y-x;
            res.second=i;
        }
    }
    int ask(int l,int r)
    {
        int st=block[l],ed=block[r];
        pii res;
        if(ed-st<=1)
        {
            res=make_pair(0,0);
            for(int i=l;i<=r;i++) update(i,l,r,res);
        }
        else
        {
            res=mode[st+1][ed-1];
            for(int i=l;i<=R[st];i++) update(i,l,r,res);
            for(int i=L[ed];i<=r;i++) update(i,l,r,res);
        }
        return res.second;
    }
    
    int main()
    {
        //freopen("input","r",stdin);
        //freopen("my","w",stdout);
    
        cin>>n>>m;
        len=max(1,(int)(n/sqrt(m*log2(n))));
        for(int i=1;i<=n;i++)
        {
            cin>>a[i];
            v.push_back(a[i]);
    
            block[i]=(i-1)/len+1;
            if(i==1) L[block[i]]=i;
            if(i==n) R[tot=block[i]]=i;
            if(2<=i && i<=n && block[i-1]!=block[i])
            {
                R[block[i-1]]=i-1;
                L[block[i]]=i;
            }
        }
        sort(v.begin(),v.end());
        v.erase(unique(v.begin(),v.end()),v.end());
        for(int i=1;i<=n;i++)
        {
            aid[i]=getid(a[i]);
            num[aid[i]].push_back(i);
        }
    
        memset(mode,0,sizeof(mode));
        for(int i=1;i<=tot;i++)
        {
            memset(cnt,0,sizeof(cnt));
            pii mx=make_pair(0,0);
            for(int k=L[i],j=block[k];k<=n;k++,j=block[k])
            {
                cnt[aid[k]]++;
                if(cnt[aid[k]]>mx.first || (cnt[aid[k]]==mx.first && a[k]<a[mx.second]))
                {
                    mx.first=cnt[aid[k]];
                    mx.second=k;
                }
                if(mx.first>mode[i][j].first || (mx.first==mode[i][j].first && a[mx.second]<a[mode[i][j].second]))
                    mode[i][j]=mx;
            }
        }
    
        int ans=0;
        while(m--)
        {
            int l,r;
            scanf("%d%d",&l,&r);
            l=(l+ans-1)%n+1;
            r=(r+ans-1)%n+1;
            if(l>r) swap(l,r);
            //printf("real l=%d r=%d
    ",l,r);
            printf("%d
    ",ans=a[ask(l,r)]);
        }
    }
  • 相关阅读:
    C:函数指针、回调函数
    C:内存分配、内存中五大区
    C:指针
    C:进制
    C:预编译指令
    C:矩形相交、相包含、相离关系判断
    C:结构体
    C:函数
    C:数组
    C: 冒泡排序
  • 原文地址:https://www.cnblogs.com/dilthey/p/9785679.html
Copyright © 2020-2023  润新知