Finding a MEX(分块&树状数组)

Finding a MEX(分块&树状数组)

强行压行,重新整理了一波代码。

思路:分块 + + 树状数组维护。
因为 m 1 e 5 m\leq 1e5 ,我们取 n = 1 e 5 n=1e5 时,显然度数大于等于 n \sqrt{n} 的个数不超过 n \sqrt{n} 个。
这些点我们记为大点。相对应 d e g [ i ] < n deg[i]<\sqrt{n} 的点为小点。

所以考虑用树状数组维护这些大点,小点进行暴力。

主要用权值树状数组维护,然后二分查找答案。

还有一些细节需要注意,比如树状数组从1开始,预先把数组 a [ ] a[] 全部加1,便于统计。

另外每个树状数组用一个桶维护当前集合不同的元素个数。

具体看代码。

时间复杂度: O ( n n ) O(n\sqrt{n})

#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;
}

猜你喜欢

转载自blog.csdn.net/weixin_45750972/article/details/107566210