【题解】[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;
}