P4168 [Violet]蒲公英

传送门

A的第一道黑题...

然而感觉跟 作诗 没什么差别...

分块

记录 sum[ i ] [ j ] 表示从左端点到第 i 块时,数字 j 的出现次数

   f [ i ] [ j ] 表示第 i 块到第 j 块的区间里的众数

对两边小段的每个数都计算贡献

设 cnt [ j ] 表示 j 在两边小段出现的次数

如果 cnt [ j ] 加上大块中 j 出现的次数大于 cnt [ ans ] 加上大块中 ans 出现的次数

或者 次数相同且 j < ans

那就更新 ans,最后就是答案了

大块中每个数的出现次数可以用sum求出

预处理时也差不多,也是动态地对每个数计算贡献

关于上面的一些细节,我前面一篇博客有比较细的说明..传送门

然后有一个问题,数的大小 ≤1e9....

所以我们要离散化

可以发现我们只要记录每个数最早出现的位置就可以知道每个数的值

那就这样搞,把每个数离散成它最早出现的位置

struct data
{
    int val,pos;//存值和位置
}a[N];
inline bool cmp(const data &a,const data &b) { return a.val!=b.val ? a.val<b.val : a.pos<b.pos; }
sort(a+1,a+n+1,cmp);//排序
int p=0;//p是当前的数最早出现的位置
for(int i=1;i<=n;i++)
{
   if(a[i].val!=a[i-1].val) p=a[i].pos;//如果当前的值与上一个值不同,就更新p
   c[a[i].pos]=p;//原序列的a[i].pos位置的数最早出现在p位置
}
离散化代码

 

然后就可以搞了,注意各种细节

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
using namespace std;
const int N=5e4+7,M=207;
inline int read()
{
    int x=0,f=1; char ch=getchar();
    while(ch<'0'||ch>'9') { if(ch=='-') f=-1; ch=getchar(); }
    while(ch>='0'&&ch<='9') { x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); }
    return x*f;
}
int n,m,b[N],c[N],ans;
struct data
{
    int val,pos;
}a[N];
inline bool cmp(const data &a,const data &b) { return a.val!=b.val ? a.val<b.val : a.pos<b.pos; }
int bel[N],L[N];//bel[i]记录位置i属于哪个块,L[i]存第i个块的左端点的位置
int sum[M][N],f[M][M];
int cnt[N];
inline void pre()
{
    for(int i=1;i<=bel[n];i++)
        for(int j=1;j<=n;j++) sum[i][j]+=sum[i-1][j];//处理sum
    int t=0;//t记录众数
    for(int i=1;i<=bel[n];i++)//枚举每个左块
    {
        t=0;
        for(int j=L[i];j<=n;j++)//枚举左块的每个右块
        {
            cnt[c[j]]++;//c[j]的出现次数+1
            if(cnt[c[j]]>cnt[t]||(cnt[c[j]]==cnt[t]&&b[c[j]]<b[t]))//如果c[j]出现次数大于t 或者 次数相同且在原数列中值更小
                t=c[j];//更新众数
            if(bel[j]!=bel[j+1]) f[i][bel[j]]=t;//如果到右块的右端点,更新f
        }
        for(int j=L[i];j<=n;j++) cnt[c[j]]--;//用完别忘了清空cnt
    }
}
inline void query(int l,int r)//处理询问
{
    ans=0;
    if(bel[l]+1>=bel[r])//如果没有大区间,暴力处理
    {
        for(int i=l;i<=r;i++)
        {
            cnt[c[i]]++;
            if(cnt[c[i]]>cnt[ans]||(cnt[c[i]]==cnt[ans]&&b[c[i]]<b[ans])) ans=c[i];
        }
        for(int i=l;i<=r;i++) cnt[c[i]]--;//别忘了清空cnt
        ans=b[ans];//别忘了ans是在原序列的值
        return;
    }
    int bl=bel[l-1]+1,br=bel[r+1]-1,t1=0,t2=0;//bl,br为大区间的最左最右块
    ans=f[bl][br];//ans的初值
    for(int i=l;i<L[bl];i++)//考虑左小段的贡献
    {
        cnt[c[i]]++;//记录每个出现的数并计算贡献
        t1=cnt[c[i]]+sum[br][c[i]]-sum[bl-1][c[i]];//计算出现的数的总出现次数
        t2=cnt[ans]+sum[br][ans]-sum[bl-1][ans];//计算原众数的出现次数
        if(t1>t2||(t1==t2&&b[c[i]]<b[ans])) ans=c[i];//尝试更新ans
    }
    for(int i=L[br+1];i<=r;i++)//同上
    {
        cnt[c[i]]++;
        t1=cnt[c[i]]+sum[br][c[i]]-sum[bl-1][c[i]];
        t2=cnt[ans]+sum[br][ans]-sum[bl-1][ans];
        if(t1>t2||(t1==t2&&b[c[i]]<b[ans])) ans=c[i];
    }
    for(int i=l;i<L[bl];i++) cnt[c[i]]--; for(int i=L[br+1];i<=r;i++) cnt[c[i]]--;//别忘了清空cnt
    ans=b[ans];//ans是在原序列中的值
}
int main()
{
    n=read(); m=read();
    int t=sqrt(n)+1;
    for(int i=1;i<=n;i++)//读入并计算bel,L
    {
        a[i].val=b[i]=read();
        a[i].pos=i; bel[i]=(i-1)/t+1;
        if(bel[i]!=bel[i-1]) L[bel[i]]=i;
        else L[bel[i]]=L[bel[i-1]];
    }
    bel[n+1]=bel[n]+1; L[bel[n+1]]=n+1;//注意细节,有时会访问到n+1的边界
    sort(a+1,a+n+1,cmp);//离散化
    int p=0;
    for(int i=1;i<=n;i++)
    {
        if(a[i].val!=a[i-1].val) p=a[i].pos;
        c[a[i].pos]=p;
    }
    for(int i=1;i<=n;i++) sum[bel[i]][c[i]]++;//先求出单个块内每个数的出现次数
    pre();//预处理sum和f
    int l,r;
    for(int i=1;i<=m;i++)
    {
        l=read(); r=read();
        l=(l+ans-1)%n+1; r=(r+ans-1)%n+1;
        if(l>r) swap(l,r);
        query(l,r);
        printf("%d\n",ans);
    }
    return 0;
}

 

posted @ 2018-10-15 10:33  LLTYYC  阅读(191)  评论(0编辑  收藏  举报