P4168 [Violet]蒲公英
P4168 [Violet]蒲公英
题目
思路
经典的在线求众数问题
预处理
首先,离散化时绝对跑不掉的,设a为离散化后的序列,c为原序列,b为离散化辅助数组
struct node {
int dat , id;
}b[nn];
bool cmp(node a , node b){return a.dat < b.dat;}
//离散化
for(int i = 1 ; i <= n ; i++)
b[i].dat = a[i] , b[i].id = i;
sort(b + 1 , b + n + 1 , cmp);
int cnt_ = 0;
for(int i = 1 ; i <= n ; i++) {
if(b[i].dat != b[i - 1].dat) cnt_++;
a[b[i].id] = cnt_;
}
把序列a分成t块(提前说明t约为3次根号下n,具体原因在时间复杂度中讲),每段长度为len=n/t
,然后是分块的常规操作:设L,R表示每一块的左右端点,pos表示每个点所属分块
t = 0;
while(t * t * t < n)++t;
len = n / t;
L[1] = 1 , R[1] = len;
for(int i = 2 ; i <= t ; i++)
L[i] = R[i - 1] + 1,
R[i] = len * i;
if(R[t] < n)
++t , L[t] = R[t - 1] + 1 , R[t] = n;
for(int i = 1 ; i <= t ; i++)
for(int j = L[i] ; j <= R[i] ; j++)
pos[j] = i;
此外,我们设cnt[i][j][k]
表示数字k在第i个块到第j个块出现的次数,zs[i][j]
表示第i个块到到j个块的众数的下标(原谅我不知道众数的英文就直接上拼音了)
//预处理cnt 和 众数
for(int i = 1 ; i <= t ; i++)
for(int j = i ; j <= t ; j++)
for(int k = L[i] ; k <= R[j] ; k++) {
++cnt[i][j][a[k]];
if(cnt[i][j][a[k]] > cnt[i][j][a[zs[i][j]]] || (cnt[i][j][a[k]] == cnt[i][j][a[zs[i][j]]] && a[k] < a[zs[i][j]]))
zs[i][j] = k;
}
对于每一个询问
若l,r属于同一块,则直接暴力
for(int i = l ; i <= r ; i++) {
++tmp_cnt[a[i]];
if(tmp_cnt[a[i]] > tmp_cnt[a[ans]] || (tmp_cnt[a[i]] == tmp_cnt[a[ans]] && a[i] < a[ans]))
ans = i;
}
for(int i = l ; i <= r ; i++)
--tmp_cnt[a[i]];//这里直接减应该比memset快(我没试过),memset是针对整个数组(就是O(m*n)了),而此时r-l不超过len,是根号级别
return c[ans];
对于其他情况:
p=pos[l],q=pos[r]
和分块模板一样,我们把[l,r]
分为:开头:[l,R[p]),中间:[L[p+1],R[q-1]],结尾:(L[q],r]
(这里注意下括号的意义,有的是下标,有的是区间)
显然,最终众数出为块p+1~q-1的众数,或开头,结尾两段的数之中
因此,我们令ans=zs[p+1][q-1]
,然后在cnt[p+1][q-1][]
的基础上加上开头,结尾两段的数,直接统计答案即可
++p , --q;//方便起见
ans = zs[p][q];
for(int i = l ; i <= R[p - 1] ; i++) {
++cnt[p][q][a[i]];
if(cnt[p][q][a[i]] > cnt[p][q][a[ans]] || (cnt[p][q][a[i]] == cnt[p][q][a[ans]] && a[i] < a[ans]))
ans = i;
}
for(int i = L[q + 1] ; i <= r ; i++) {
++cnt[p][q][a[i]];
if(cnt[p][q][a[i]] > cnt[p][q][a[ans]] || (cnt[p][q][a[i]] == cnt[p][q][a[ans]] && a[i] < a[ans]))
ans = i;
}
for(int i = l ; i <= R[p - 1] ; i++)--cnt[p][q][a[i]];//这两行用于清除开头,结尾的影响
for(int i = L[q + 1] ; i <= r ; i++)--cnt[p][q][a[i]];
return c[ans];
时空复杂度
时间复杂度(前半段为预处理,后半段为每一次查询):
\[O(nt^2+m\frac{n}{t})
\]
题目已经给出m,n在一个数量级(最大数据),因此,我们考虑让两边尽量平均,则有方程组
\[nt^2=m\frac{n}{t}
\]
解得t等于三次根号下m,约等于三次根号下n
空间复杂度:
\[O(nt^2)
\]
由于t才去到30~40,所以是可以接受的
代码(含对拍)
声明:使用对拍文件时需要关掉强制在线,即直接输入l,r(详情参考std.cpp)
第一次交时忘记了强制在线(听取WA声一片),曾一度怀疑人生:难道我暴力都写错了?
不然就一次AC了
话说样例真水啊,没开强制在线都能过
AC代码(tested.cpp)
#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
#define nn 40010
#define max_t 64+10
using namespace std;
int read() {
int re = 0;
bool sig = false;
char c = getchar();
while(c < '0' || c > '9') {
if(c == '-')sig = true;
c = getchar();
}
while(c >= '0' && c <= '9')
re = (re << 1) + (re << 3) + c - '0',
c = getchar();
return sig ? -re : re;
}
int n , m;
int t , len;
int a[nn];
int c[nn];
int zs[max_t][max_t];
int L[max_t] , R[max_t];
int pos[nn];
unsigned short cnt[max_t][max_t][nn];
struct node {
int dat , id;
}b[nn];
bool cmp(node a , node b){return a.dat < b.dat;}
void Init() {
//离散化
for(int i = 1 ; i <= n ; i++)
b[i].dat = a[i] , b[i].id = i;
sort(b + 1 , b + n + 1 , cmp);
int cnt_ = 0;
for(int i = 1 ; i <= n ; i++) {
if(b[i].dat != b[i - 1].dat) cnt_++;
a[b[i].id] = cnt_;
}
//分块
t = 0;
while(t * t * t < n)++t;
len = n / t;
L[1] = 1 , R[1] = len;
for(int i = 2 ; i <= t ; i++)
L[i] = R[i - 1] + 1,
R[i] = len * i;
if(R[t] < n)
++t , L[t] = R[t - 1] + 1 , R[t] = n;
for(int i = 1 ; i <= t ; i++)
for(int j = L[i] ; j <= R[i] ; j++)
pos[j] = i;
//预处理cnt 和 众数
for(int i = 1 ; i <= t ; i++)
for(int j = i ; j <= t ; j++)
for(int k = L[i] ; k <= R[j] ; k++) {
++cnt[i][j][a[k]];
if(cnt[i][j][a[k]] > cnt[i][j][a[zs[i][j]]] || (cnt[i][j][a[k]] == cnt[i][j][a[zs[i][j]]] && a[k] < a[zs[i][j]]))
zs[i][j] = k;
}
}
int tmp_cnt[nn];
int query(int l , int r) {
int p = pos[l] , q = pos[r];
int ans = 0;
if(p == q) {
for(int i = l ; i <= r ; i++) {
++tmp_cnt[a[i]];
if(tmp_cnt[a[i]] > tmp_cnt[a[ans]] || (tmp_cnt[a[i]] == tmp_cnt[a[ans]] && a[i] < a[ans]))
ans = i;
}
for(int i = l ; i <= r ; i++)
--tmp_cnt[a[i]];
return c[ans];
}
++p , --q;
ans = zs[p][q];
for(int i = l ; i <= R[p - 1] ; i++) {
++cnt[p][q][a[i]];
if(cnt[p][q][a[i]] > cnt[p][q][a[ans]] || (cnt[p][q][a[i]] == cnt[p][q][a[ans]] && a[i] < a[ans]))
ans = i;
}
for(int i = L[q + 1] ; i <= r ; i++) {
++cnt[p][q][a[i]];
if(cnt[p][q][a[i]] > cnt[p][q][a[ans]] || (cnt[p][q][a[i]] == cnt[p][q][a[ans]] && a[i] < a[ans]))
ans = i;
}
for(int i = l ; i <= R[p - 1] ; i++)--cnt[p][q][a[i]];
for(int i = L[q + 1] ; i <= r ; i++)--cnt[p][q][a[i]];
return c[ans];
}
int main() {
n = read(), m = read();
for(int i = 1 ; i <= n ; i++)
c[i] = a[i] = read();
Init();
int lastans = 0;
for(int i = 1 ; i <= m ; i++) {
int l , r;
l = read(), r = read();
l = ((l + lastans - 1) % n) + 1;
r = ((r + lastans - 1) % n) + 1;
if(l > r) {int tmp = l ; l = r ; r = tmp;}
// query(l , r);
printf("%d\n" , lastans = query(l , r));
}
return 0;
}
暴力(std.cpp)
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define nn 40010
using namespace std;
int read() {
int re = 0;
bool sig = false;
char c = getchar();
while(c < '0' || c > '9') {
if(c == '-')sig = true;
c = getchar();
}
while(c >= '0' && c <= '9')
re = (re << 1) + (re << 3) + c - '0',
c = getchar();
return sig ? -re : re;
}
int n , m ;
int a[nn] , c[nn];
struct node {
int dat , id;
}b[nn];
bool cmp(node a , node b){return a.dat < b.dat;}
int cnt[nn];
int main() {
n = read(), m = read();
for(int i = 1 ; i <= n ; i++)
a[i] = b[i].dat = c[i] = read(), b[i].id = i;
sort(b + 1 , b + n + 1 , cmp);
int cnt_ = 0;
for(int i = 1 ; i <= n ; i++) {
if(b[i].dat != b[i - 1].dat) cnt_++;
a[b[i].id] = cnt_;
}
for(int i = 1 ; i <= m ; i++) {
int l =read() , r = read();
memset(cnt , 0 , sizeof(cnt));
int ans = 0;
for(int j = l ; j <= r ; j++) {
cnt[a[j]]++;
if(cnt[a[j]] > cnt[a[ans]] || (cnt[a[j]] == cnt[a[ans]] && a[j] < a[ans]))
ans = j;
}
printf("%d\n" , c[ans]);
}
return 0;
}
随机数据(random.cpp)
#include <bits/stdc++.h>
using namespace std;
int random(int r , int l = 1) {
return (l == r ? l : ((long long)rand() * rand() % (r - l) + l ));
}
int main() {
srand((unsigned)time(0));
int n = 40000 , m = 50000;
printf("%d %d\n" , n , m);
for(int i = 1 ; i <= n ; i++)
printf("%d " , random(1000000000));
putchar('\n');
for(int i = 1 ; i <= m ; i++) {
int l = random(n) , r = random(n , l);
printf("%d %d\n" , l , r);
}
return 0;
}
对拍控制(compare.cpp)
将continue
删掉,std
的双斜杠去掉,即开启std和tested的对拍
#include <bits/stdc++.h>
using namespace std;
int main() {
while(true) {
system("random.exe > input.txt");
puts("random");
// system("std.exe < input.txt > output1.txt");
// puts("std");
int t = clock();
if(system("tested.exe < input.txt > output2.txt") != 0) {//检验运行时错误,记得return 0
cout << "RE";
return 0;
}
puts("tested");
printf(">time:%d\n" , clock() - t);
continue;
if(system("fc output1.txt output2.txt")) {
cout << "WA";
system("start input.txt");
return 0;
}
}
return 0;
}