Finding a MEX(分块&树状数组)
强行压行,重新整理了一波代码。
思路:分块
树状数组维护。
因为
,我们取
时,显然度数大于等于
的个数不超过
个。
这些点我们记为大点。相对应
的点为小点。
所以考虑用树状数组维护这些大点,小点进行暴力。
主要用权值树状数组维护,然后二分查找答案。
还有一些细节需要注意,比如树状数组从1开始,预先把数组 全部加1,便于统计。
另外每个树状数组用一个桶维护当前集合不同的元素个数。
具体看代码。
时间复杂度:
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
#define lowbit(x) x&(-x)
#define pb push_back
struct BIT{
int tr[N],b[N],sz;//tr[]树状数组,b[]桶,sz大小.
void init(int n){ sz=n; //初始化.
for(int i=1;i<=n;i++) tr[i]=b[i]=0;
}
void add(int i,int t){
b[i]+=t;
if((b[i]==1&&t==1)||(b[i]==0&&t==-1)) //权值个数发生变化才更新tr[].
while(i<=sz) tr[i]+=t,i+=lowbit(i);
}
int ask(int i){ //求和.
int ans=0;
while(i)ans+=tr[i],i-=lowbit(i);
return ans;
}
int Mex(){ //大点进行二分查找.
int l=0,r=sz,ans=1;
while(l<=r){
int mid=(l+r)>>1;
if(ask(mid)==mid)ans=mid,l=mid+1;
else r=mid-1;
}
return ans+1;
}
}B[405];
vector<int>g[N],h[N];//h[i]储存与结点i相连的大点(heavy vertex)
int tot,n,m,d[N],a[N],id[N],q,bk;//tot表示大点个数,id[i]表示结点i对应的大点编号,bk表示块的大小.
signed main(){
int T;scanf("%d",&T);
while(T--){
scanf("%d%d",&n,&m);bk=sqrt(n)+1;tot=0; //初始化
for(int i=1;i<=n;i++)
scanf("%d",&a[i]),++a[i],g[i].clear(),h[i].clear(),id[i]=d[i]=0; //从1开始权值便于统计.
for(int i=1,u,v;i<=m;i++)
scanf("%d%d",&u,&v),g[u].pb(v),g[v].pb(u),d[u]++,d[v]++;
for(int i=1;i<=n;i++){
if(d[i]>bk){ //处理大点
id[i]=++tot,B[tot].init(d[i]);
for(auto v:g[i]){
h[v].pb(i);//存储相邻大点
if(a[v]<=d[i]) B[tot].add(a[v],1);//更新权值.
}
}
}
scanf("%d",&q);
while(q--){
int op,x;scanf("%d",&op);
if(op==1){ //modify
int val;scanf("%d%d",&x,&val),val++;
for(int v:h[x]){ //修改大点.
if(a[x]<=d[v]) B[id[v]].add(a[x],-1);//删除之前的.
if(val<=d[v]) B[id[v]].add(val,1);//加入当前的.
}a[x]=val;
}
else if(op==2){//ask
scanf("%d",&x);
if(d[x]>bk) printf("%d\n",B[id[x]].Mex()-1); //如果是大点.
else{ set<int>s;int ans=1; //小点直接暴力.
for(int v:g[x]) s.insert(a[v]);
for(int i:s)
if(i==ans)ans++;
else break;
printf("%d\n",ans-1);
}
}
}
}
return 0;
}