1. 程式人生 > 實用技巧 >隨機化的正確開啟方式

隨機化的正確開啟方式

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;
}