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;
}
posted @ 2020-11-25 21:00  追梦人1024  阅读(87)  评论(0编辑  收藏  举报