题解【luogu4168 [Violet]蒲公英】

Description

给出一个长度为 \(n\) 序列 \(a\) ,\(m\) 次询问,每次询问区间 \([l,r]\) 里的众数(出现次数最多的数)。若有多个,输出最小的。

\(a_i \leq 10^9, n \leq 40000, m \leq 50000\),强制在线。

Solution

\(a_i \leq 10^9\) ,先离散化。然后

算法一:暴力 \(n ^ 2\) ,预计得分 20 ; 实际得分 20 (开了 O2 直接变成 85 啥操作)

算法二:\(n \leq 40000\) , 看来需要搬出分块大法。

预处理出两个数组:

\(p[i][j]\):表示第 \(i\) 个块 到第 \(j\) 个块的(最小的)众数。

\(s[i][j]\):类似于前缀和,在前 \(i\) 个(包括 \(i\) )个块中 \(j\) (离散化之后的值)出现了几次。

如何预处理 \(p,s\)

对于 \(s\) ,直接每个块扫一遍,复杂度 \(O(n \sqrt n)\)

对于 \(p\) ,双重循环枚举 \(i,j\),开一个数组暴力统计每个数出现了多少次。复杂度 \(O(\sqrt n \sqrt n \sqrt n)=O(n \sqrt n)\)

预处理 \(p,s\) 有啥用呢?对于一个询问 \([l,r]\) ,设 \(l\) 在第 \(posl\) 个块中,\(r\) 在第 \(posr\) 个块中。那么分两种情况:

第一种:\(posr - posl <= 1\),直接暴力扫 \(l,r\),复杂度 \(O(\sqrt n)\)

第二种:\(posr - posl >= 2\),如下图:

红线就是 \(l\),蓝线就是 \(r\),黑线是块与块的分割线。

答案 \(\in\) \(\{\text{黄线中的元素}\} \cup \{\text{绿线的众数}\}\)

绿线的众数在之前已经预处理好了,对于黄线中的每一个元素在区间\([l,r]\)中出现的次数就是 在黄线中出现的次数 + 在绿线中出现的次数。

对于在黄线中出现的次数,可以直接扫,复杂度 \(O(\sqrt n)\)

对于在绿线中出现的次数,可以根据之前处理的前缀和算出。

这样每个元素就可以在 \(O(\sqrt n)\) 的时间内求出出现次数,然后就可以愉快的AC神仙分块黑题了了。 (细节很多,调了很久)

Code

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

using namespace std;
const int N = 40040;
const int K = 220;
int n, m, L, len, sum[K][N], vis[N];
int tmpnum[N], B[N], last, pre[N];
struct getin {
    int id, d, se;
}a[N];
struct node {
    int num, s;
}p[K][K];
inline bool cmp1(getin x, getin y) { return x.d < y.d; }
inline bool cmp2(getin x, getin y) { return x.id < y.id; }
inline int getB(int x) {
    int ret = x / L;
    if(x % L) ret++;
    return ret;
}
inline void prework() {
    for(int i = 1; i <= len; i++) {
        memset(B, 0, sizeof(B)); node tmp;
        tmp.num = tmp.s = 0;
        for(int j = i; j <= len; j++) {
            for(int k = (j - 1) * L + 1; k <= min(n, j * L); k++) {
                B[a[k].se]++;
                if(B[a[k].se] > tmp.s) {
                    tmp.num = a[k].se;
                    tmp.s = B[a[k].se];
                }
                else if(B[a[k].se] == tmp.s)
                    tmp.num = min(tmp.num, a[k].se),
                    tmp.s = B[a[k].se];
            }
            p[i][j] = tmp;
        }
    }
    for(int i = 1; i <= len; i++) {
        for(int j = 1; j <= n; j++) sum[i][a[j].se] = sum[i - 1][a[j].se];
        for(int j = (i - 1) * L + 1; j <= min(n, i * L); j++)
            sum[i][a[j].se]++;
    }
}
int main() {
    scanf("%d%d", &n, &m); L = sqrt(n);
    len = (n + L - 1) / L;
    for(int i = 1; i <= n; i++)
        scanf("%d", &a[i].d), a[i].id = i;
    sort(a + 1, a + n + 1, cmp1); a[0].d = -1;
    for(int i = 1; i <= n; i++) {
        a[i].se = a[i - 1].se;
        if(a[i - 1].d != a[i].d)
            a[i].se++;
        pre[a[i].se] = a[i].d;
    }
    sort(a + 1, a + n + 1, cmp2);
    prework();
    for(int i = 1; i <= m; i++) {
        int l, r; scanf("%d%d", &l, &r);
        l = (l + last - 1) % n + 1;
        r = (r + last - 1) % n + 1;
        if(l > r) swap(l, r);
        int posl = getB(l), posr = getB(r);
         if(posr - posl <= 2) {
            int ans = 0;
            for(int j = l; j <= r; j++) tmpnum[a[j].se] = 0;
            for(int j = l; j <= r; j++) {
                tmpnum[a[j].se]++;
                if(tmpnum[a[j].se] > tmpnum[ans]) ans = a[j].se;
                else if(tmpnum[a[j].se] == tmpnum[ans]) ans = min(ans, a[j].se);
            }
            printf("%d\n", last = pre[ans]);
        }
        else {
            int ans = p[posl + 1][posr - 1].num;
            tmpnum[ans] = 0, vis[ans] = 0;
            for(int j = l; j <= min(n, posl * L); j++) tmpnum[a[j].se] = 0, vis[a[j].se] = 0;
            for(int j = (posr - 1) * L + 1; j <= r; j++) tmpnum[a[j].se] = 0, vis[a[j].se] = 0;
            for(int j = l; j <= min(n, posl * L); j++) tmpnum[a[j].se]++;
            for(int j = (posr - 1) * L + 1; j <= r; j++) tmpnum[a[j].se]++;
            int MXnum, MX = 0;
            for(int j = l; j <= min(n, posl * L); j++)
                if(!vis[a[j].se]) {
                    vis[a[j].se] = 1;
                    int val = tmpnum[a[j].se] + sum[posr - 1][a[j].se] - sum[posl][a[j].se];
                    if(MX < val)
                        MX = val,
                        MXnum = a[j].se;
                    else if(MX == val) MXnum = min(MXnum, a[j].se);
                }
            for(int j = (posr - 1) * L + 1; j <= r; j++)
                if(!vis[a[j].se]) {
                    vis[a[j].se] = 1;
                    int val = tmpnum[a[j].se] + sum[posr - 1][a[j].se] - sum[posl][a[j].se];
                    if(MX < val)
                        MX = val,
                        MXnum = a[j].se;
                    else if(MX == val) MXnum = min(MXnum, a[j].se);
                }
            if(MX > tmpnum[ans] + p[posl + 1][posr - 1].s) ans = MXnum;
            else if(MX == tmpnum[ans] + p[posl + 1][posr - 1].s) ans = min(ans, MXnum);
            printf("%d\n", last = pre[ans]);
        }
    }
    return 0;
}

原文地址:https://www.cnblogs.com/TLE666/p/10051345.html

时间: 2024-10-09 15:06:31

题解【luogu4168 [Violet]蒲公英】的相关文章

Luogu P4168 [Violet]蒲公英

P4168 [Violet]蒲公英 题意 题目背景 亲爱的哥哥: 你在那个城市里面过得好吗? 我在家里面最近很开心呢.昨天晚上奶奶给我讲了那个叫「绝望」的大坏蛋的故事的说!它把人们的房子和田地搞坏,还有好多小朋友也被它杀掉了.我觉得把那么可怕的怪物召唤出来的那个坏蛋也很坏呢.不过奶奶说他是很难受的时候才做出这样的事的-- 最近村子里长出了一大片一大片的蒲公英.一刮风,这些蒲公英就能飘到好远的地方了呢.我觉得要是它们能飘到那个城市里面,让哥哥看看就好了呢! 哥哥你要快点回来哦! 爱你的妹妹 \(V

[Luogu P4168] [Violet]蒲公英 (分块)

题面 洛咕 Solution 题目要求求出区间众数,强制在线. 区间众数是一个比较尴尬的问题,我们无法用区间数据结构来处理这个问题,因为我们没法很好的合并区间众数的答案. 既然区间数据结构解决不了这个问题,我们可以考虑一下使用基于分块的算法,例如莫队. 这题用莫队非常好处理,不幸的是,这题要求强制在线. 因此我们考虑使用分块算法. 分块算法的核心在于把一整个块的信息压缩起来以便快速处理. 我们要查询一段区间的众数,我们可以考虑这样搞:对于这个区间内连续的块,我们先快速地查询这个连续的块中的众数,

[violet]蒲公英题解

前几天刚学习了分块,感觉这道题用分块求解的方式挺巧妙的 既然用的是分块,那么肯定是两端暴力求解,中间要快速地处理每个块 首先我们要得到一个结论,最后求出的这一个众数必定为中间块的众数或者是两端的任意一个数,那么我们用\(nu[i][j]\)来表示第\(i\)个块到第\(j\)个块的众数,我们可以用用\(O(n\sqrt{n})\)的时间复杂度,先枚举每个块,然后枚举后面的点来求出\(nu[i][j]\),放一下这一段的代码 void pre(int x) { memset(cnt,0,sizeo

[Violet]蒲公英

题意: 给出一个长度为 \(n\) 序列\(a\) ,\(m\)次询问,每次询问区间 \(l,r\) 里的众数(出现次数最多的数).若有多个,输出最小的. \(a_i \leq 10^9, n \leq 40000, m \leq 50000\),强制在线. 题解: 看了题解才懂的.根据https://www.cnblogs.com/acfunction/p/10051345.html hzwer给出了更巧妙的方法http://hzwer.com/3582.html \(a_i \le 10^9

[Violet]蒲公英 分块

Code: #include<cstdio> #include<string> #include<iostream> #include<algorithm> #include<vector> #include<cstring> #include<cmath> using namespace std; void setIO(string a){ freopen((a+".in").c_str(),&quo

p4168 [Violet]蒲公英(分块)

区间众数的重题 和数列分块入门9双倍经验还是挺好的 然后开O2水过 好像有不带log的写法啊 之后在补就是咕咕咕 // luogu-judger-enable-o2 #include <cstdio> #include <algorithm> #include <cstring> #include <vector> #include <map> #include <cmath> using namespace std; int m,b

P4168 [Violet]蒲公英 分块

这道题算是好好写了.写了三种方法. 有一个好像是$qwq$$N\sqrt(N)$的方法,,但是恳请大佬们帮我看看为什么这么慢$qwq$(后面的第三种) 注:$pos[i]$表示$i$属于第$pos[i]$块. 第一种是统计所有可能的块组成的区间中(第i块到第j块),每个数出现的次数,记做$f[i][j][k]$,和所有可能的块组成的区间的答案,记做$h[i][j]$. 然后每次先把整块的答案作为初始答案,然后对于散块中的每个值$vl$,暴力修改对应的$f[i][j][vl]$,更新答案. 当块长

分块简单入门

分块简单入门 按 树状数组虽超级快,但是很僵硬,不灵活:线段树虽快,但是却不直观,代码量大:所以,速度较慢但直观形象.代码量小的分块大法不实为线段树的替代品. 网络上关于分块的教程不知道为什么很少,虽然早有hzwer大神的分块九讲,但是还是少了入门级详解教程.此篇将分为三个阶段,保证初学者在有意识地阅读后基本掌握分块. 1.简单的入门题 问题引入 给定长度为N(N<=1e5)的正数数列A,然后输入Q(Q<=1e5)行操作命令,指令形如Q l r,表示统计数列中第l~r个数中的最大值 思路 首先

BZOJ2724: [Violet 6]蒲公英

2724: [Violet 6]蒲公英 Time Limit: 40 Sec  Memory Limit: 512 MBSubmit: 795  Solved: 248[Submit][Status] Description Input 修正一下 l = (l_0 + x - 1) mod n + 1, r = (r_0 + x - 1) mod n + 1 Output Sample Input Sample Output HINT 修正下: n <= 40000, m <= 50000 S