隨機化的正確開啟方式
阿新 • • 發佈:2020-11-23
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;
}