【题解】[NOI2022] 众数

[NOI2022] 众数

题目分析:

解法一:

一个很常规的解法就是摩尔投票,也就是所谓的对拼消耗,因为绝对众数只可能是摩尔投票最后剩下的那个数。
这样的话对于查询操作,我们只需要将 \(x_1,x_2,\cdots,x_m\) 的摩尔投票结果合并,然后查询最后剩下的数的出现次数是否满足绝对众数的条件就好了。
对于操作 \(1,2\) 就是很典型的线段树操作了,对于操作 \(4\) 就是线段树合并也很简单。
其实在维护这个之外我们还要维护序列,而对于操作 \(4\) 的合并而言,显然我们直接维护链表就可以了,链表的合并可是 \(O(1)\) 的。
但是也就是这个维护序列造成了著名的 \(deque\) 惨案,也就是很多人选择操作 \(4\) 使用 \(deque\) 去启发式合并实现,当然复杂度是没问题的,但是这样需要开 \(1e6\)\(deque\),但是 \(deque\) 不像 \(vector\) 不插入数就没空间,只要有哪怕不插入数都有空间占用,直接就直接 MLE 然后爆零了。 、

解法二:

我们考虑绝对众数一定是中位数,所以可以在线段树上二分得到中位数是什么,然后判断中位数出现的次数是否严格大于长度的一半。

解法三:

个人感觉比解法一实现起来简单。
因为如果一个区间内存在绝对众数,那么必然只有一个数满足条件,所以就可以直接根据这个在线段树上二分。
每次找到左/右区间哪一个符合数的数量严格大于一半,最后找到的数就是唯一有可能的了。

个人感觉三种解法的本质都是一样的,都是在操作 \(3\) 上进行的不同的解决方式,但也都是应用了绝对众数的相关性质。

代码(解法三):

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 2e6+5;
int tot,cnt,m,l[N],r[N],sz[N],x[N],head[N],tail[N],rt[N],val[N],lson[10*N],rson[10*N],sum[10 * N];
void modify(int &now,int now_l,int now_r,int pos,int val){
	if(!now)	now = ++cnt;
	if(now_l == now_r){
		sum[now] += val;
		return;
	}
	int mid = (now_l + now_r)>>1;
	if(pos <= mid)	modify(lson[now],now_l,mid,pos,val);
	if(pos > mid)	modify(rson[now],mid+1,now_r,pos,val);
	sum[now] = sum[lson[now]] + sum[rson[now]];
}
void merge(int &now,int lst,int now_l,int now_r){
	if(!now || !lst){
		if(!now)	now = lst;
		return;
	}
	if(now_l == now_r){
		sum[now] += sum[lst];
		return;
	}
	int mid = (now_l + now_r)>>1;
	merge(lson[now],lson[lst],now_l,mid);
	merge(rson[now],rson[lst],mid+1,now_r);
	sum[now] = sum[lson[now]] + sum[rson[now]];
}
int get(int h){
	int ans = 0;
	if(h == 0)	for(int i=1; i<=m; i++)	ans += sum[lson[x[i]]];
	else	for(int i=1; i<=m; i++)	ans += sum[x[i]];
	return ans;
}
void mover(){
	for(int i=1; i<=m; i++)	x[i] = rson[x[i]];
}
void movel(){
	for(int i=1; i<=m; i++)	x[i] = lson[x[i]];
}
int query(int now_l,int now_r,int k){
	if(now_l == now_r){
		if(get(1) < k)	return -1;
		return now_l;
	}
	int mid = (now_l + now_r)>>1;
	int ans = get(0);
	if(ans < k){
		mover();
		return query(mid+1,now_r,k);
	}
	movel();
	return query(now_l,mid,k);
}
void insert(int x,int p){
	++tot;
	l[tot] = tail[x];r[tail[x]] = tot;
	tail[x] = tot;
	if(!sz[x])	head[x] = tot;
	val[tot] = p;sz[x]++;
}
void del(int x){
	tail[x] = l[tail[x]];sz[x]--;
	if(!sz[x])	head[x] = 0;
}
void link(int x,int y,int z){
	head[z] = sz[x] ? head[x] : head[y];
	tail[z] = sz[y] ? tail[y] : tail[x];
	sz[z] = sz[x] + sz[y];
	if(head[y])	l[head[y]] = tail[x];
}
signed main(){
//	freopen("in.txt","r",stdin);
//	freopen("out.txt","w",stdout);
	int n,q;scanf("%lld%lld",&n,&q);
	for(int i=1; i<=n; i++){
		int len;scanf("%lld",&len);
		for(int j=1; j<=len; j++){
			int p;scanf("%lld",&p);
			modify(rt[i],1,n+q,p,1);
			insert(i,p);
		}
	}
	for(int i=1; i<=q; i++){
		int opt;scanf("%lld",&opt);
		if(opt == 1){
			int x,y;scanf("%lld%lld",&x,&y);
			modify(rt[x],1,n+q,y,1);
			insert(x,y);
		}
		else if(opt == 2){
			int x;scanf("%lld",&x);
			modify(rt[x],1,n+q,val[tail[x]],-1);
			del(x);
		}
		else if(opt == 3){
			scanf("%lld",&m);
			for(int i=1; i<=m; i++)	scanf("%lld",&x[i]),x[i] = rt[x[i]];
			int tmp = 0;
			for(int i=1; i<=m; i++)	tmp += sum[x[i]];
			printf("%lld\n",query(1,n+q,tmp/2+1));
		}
		else if(opt == 4){
			int x1,x2,x3;scanf("%lld%lld%lld",&x1,&x2,&x3);
			merge(rt[x3],rt[x1],1,n+q);
			merge(rt[x3],rt[x2],1,n+q);
			link(x1,x2,x3);
		}
	}
	return 0;
}
posted @ 2023-03-13 16:52  linyihdfj  阅读(76)  评论(0编辑  收藏  举报