P8496 [NOI2022] 众数 题解
LG8496 [NOI2022] 众数
对于一个序列,定义其众数为序列中出现次数严格大于一半的数字。注意该定义与一般的定义有出入,在本题中请以题面中给出的定义为准。
一开始给定 \(n\) 个长度不一的正整数序列,编号为 \(1 \sim n\),初始序列可以为空。这 \(n\) 个序列被视为存在,其他编号对应的序列视为不存在。
有 \(q\) 次操作,操作有以下类型:
- \(1 \ x \ y\):在 \(x\) 号序列末尾插入数字 \(y\)。保证 \(x\) 号序列存在,且 \(1 \le x, y \le n + q\)。
- \(2 \ x\):删除 \(x\) 号序列末尾的数字,保证 \(x\) 号序列存在、非空,且 \(1 \le x \le n + q\)。
- \(3 \ m \ x_1 \ x_2 \ x_m\):将 \(x_1, x_2, \ldots, x_m\) 号序列顺次拼接,得到一个新序列,并询问其众数。如果不存在满足上述条件的数,则返回 \(-1\)。数据保证对于任意 \(1 \le i \le m\),\(x_i\) 是一个仍然存在的序列,\(1 \le x_i \le n + q\),且拼接得到的序列非空。注意:不保证 \(\boldsymbol{x_1, \ldots, x_m}\) 互不相同,询问中的合并操作不会对后续操作产生影响。
- \(4 \ x_1 \ x_2 \ x_3\):新建一个编号为 \(x_3\) 的序列,其为 \(x_1\) 号序列后顺次添加 \(x_2\) 号序列中数字得到的结果,然后删除 \(x_1, x_2\) 对应的序列。此时序列 \(x_3\) 视为存在,而序列 \(x_1, x_2\) 被视为不存在,在后续操作中也不会被再次使用。保证 \(1 \le x_1, x_2, x_3 \le n + q\)、\(x_1 \ne x_2\)、序列 \(x_1, x_2\) 在操作前存在、且在操作前没有序列使用过编号 \(x_3\)。
不容易发现(也有可能是容易发现,反正我没发现),一个数列的严格众数如果存在,一定为这个数列的中位数。
对每个数列维护一颗动态开点的权值线段树,和一个链表。
对于操作 \(1,2\),分别对权值线段树和链表进行修改即可。
对于操作 \(3\),注意 \(\sum m\leq 5\times 10^5\),所以我们直接对每个数列的权值线段树进行线段树二分找中位数即可。找到中位数之后判断一下时候合法即可。总时间复杂度 \(O(m\log V)\)
对于操作 \(4\),合并链表,可以做到 \(O(1)\)。线段树合并,总时间复杂度 \(O(n\log V)\)。
所以总的时间复杂度为 \(O(n\log n)\) 级别。(题目给定的所有信息均为 \(10^6\) 级别)
#include <iostream>
#include <cstdio>
#include <cstring>
#include <vector>
using namespace std;
typedef long long ll;
inline ll read(){
ll x=0,f=1;char ch=getchar();
while(ch<'0'||'9'<ch){if(ch=='-')f=-1;ch=getchar();}
while('0'<=ch&&ch<='9'){x=(x<<3)+(x<<1)+(ch^48);ch=getchar();}
return x*f;
}
const int N=1000006;
int n,q,m,que[N],lis[N],lim;
int head[N],tail[N],pre[N],val[N],tot;ll siz[N];
inline void addlink(int id,int u){
if(!siz[id])head[id]=u;
++siz[id];
pre[u]=tail[id];tail[id]=u;
}
inline void dellink(int id){
--siz[id];if(!siz[id])head[id]=0;
tail[id]=pre[tail[id]];
}
inline void link(int u,int v,int id){
head[id]=(siz[u]?head[u]:head[v]);
tail[id]=(siz[v]?tail[v]:tail[u]);
siz[id]=siz[u]+siz[v];
if(head[v])pre[head[v]]=tail[u];
}
int rt[N],lc[N*21],rc[N*21],ttt;ll sum[N*21];
#define mid ((l+r)>>1)
inline void pushup(int p){sum[p]=sum[lc[p]]+sum[rc[p]];}
void update(int &p,int l,int r,int L,ll v){
if(!p)p=++ttt;
if(l==r){sum[p]+=v;return;}
if(L<=mid)update(lc[p],l,mid,L,v);
else update(rc[p],mid+1,r,L,v);
pushup(p);
}
ll getsum(int p,int l,int r,int L){
if(!p)return 0;
if(l==r)return sum[p];
if(L<=mid)return getsum(lc[p],l,mid,L);
return getsum(rc[p],mid+1,r,L);
}
int merge(int x,int y,int l,int r){
if(!x||!y)return x+y;
if(l==r){sum[x]+=sum[y];return x;}
lc[x]=merge(lc[x],lc[y],l,mid);
rc[x]=merge(rc[x],rc[y],mid+1,r);
pushup(x);return x;
}
int query(int k,int l,int r){
if(l==r)return l;
ll tmp=0;
for(int i=1;i<=m;++i)
tmp+=sum[lc[lis[i]]];
if(tmp>=k){
for(int i=1;i<=m;++i)
lis[i]=lc[lis[i]];
return query(k,l,mid);
}else{
for(int i=1;i<=m;++i)
lis[i]=rc[lis[i]];
return query(k-tmp,mid+1,r);
}
}
int main(){
n=read(),q=read();lim=n+q;
for(int i=1;i<=n;++i){
m=read();
for(int j=1;j<=m;++j){
int x=read();val[++tot]=x;
addlink(i,tot);
update(rt[i],1,lim,x,1);
}
}
while(q--){
int opt=read();
if(opt==1){
int id=read(),x=read();
val[++tot]=x;
addlink(id,tot);
update(rt[id],1,lim,x,1);
}else if(opt==2){
int id=read();
update(rt[id],1,lim,val[tail[id]],-1);
dellink(id);
}else if(opt==3){
m=read();ll all=0;
for(int i=1;i<=m;++i){
que[i]=read();
lis[i]=rt[que[i]];
all+=siz[que[i]];
}
int loc=query((all+1)>>1,1,lim);ll tmp=0;
for(int i=1;i<=m;++i)
tmp+=getsum(rt[que[i]],1,lim,loc);
if(tmp*2>all)printf("%d\n",loc);
else puts("-1");
}else{
int u=read(),v=read(),id=read();
link(u,v,id);rt[id]=merge(rt[u],rt[v],1,lim);
}
}
return 0;
}
本文作者:BigSmall_En
本文链接:https://www.cnblogs.com/BigSmall-En/p/16636162.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek “源神”启动!「GitHub 热点速览」
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
· DeepSeek R1 简明指南:架构、训练、本地部署及硬件要求
· 2 本地部署DeepSeek模型构建本地知识库+联网搜索详细步骤