*Finding a MEX【轻重点分块】-2020杭电多校1
题意:
给出一个图,\(n\) 个点 \(m\) 条边,每个点有一个权值 \(A_i\),定义 \(F(u)\) 为 \(mex\{A_v|(u,v)\in E \}\)。有两种操作:
1.将 \(A_u\) 的值改为 \(x\);
2.求出 \(F(u)\) 的值;
分析:
首先要明确点 \(F(u)\) 的值一定不会超过点 \(u\) 的度。选定一个值为度的界限,当点的度超过该值时,视为重点,否则为轻点。对于重点,由于其相连的点的个数比较多,所以用一个 \(set\) 维护,保证 \(set\) 的第一个数就是答案;对于轻点,由于相连的点比较少,可以采用直接暴力的方式。通过这种方法,可以降低复杂度。
注意要记录每个数的出现的次数,界限的选取和边的数量有关,此处选 \(1000\)。
代码:
#include <bits/stdc++.h>
#define pb push_back
using namespace std;
const int N=1e5+5;
int a[N],degree[N];//点权和每个点的度
vector<int>pic[N];
int block=1000;
set<int>st[N];
int vis[1010],num[110][N],id[N];//只存重点周围的权值出现的次数
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
int n,m,u,v,x,q,op;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
pic[i].clear();
degree[i]=0;
id[i]=0;
}
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
for(int i=1;i<=m;i++)
{
scanf("%d%d",&u,&v);
pic[u].pb(v);
pic[v].pb(u);
degree[u]++,degree[v]++;//记下每个点的度
}
//要记录权值出现的次数
int cnt=0;
for(int i=1;i<=n;i++)
{
if(degree[i]>=block)//重点
{
id[i]=++cnt;//记该点是第几个重点
st[cnt].clear();//保证第一个元素为答案
for(int j=0;j<=degree[i];j++)
{
st[cnt].insert(j);
num[cnt][j]=0;
}
for(int j=0;j<pic[i].size();j++)
{
int t=pic[i][j];
if(a[t]<=degree[i])//答案不会超过degree[i]
{
num[cnt][a[t]]++;
st[cnt].erase(a[t]);
}
}
}
}
scanf("%d",&q);
while(q--)
{
scanf("%d",&op);
if(op==1)
{
scanf("%d%d",&u,&x);
for(int i=0;i<pic[u].size();i++)
{
int t=pic[u][i];
if(id[t]==0) continue;//不对轻点进行处理
if(degree[t]>=block)
{
if(x<=degree[t])
{
num[id[t]][x]++;
st[id[t]].erase(x);
}
if(a[u]<=degree[t])
{
num[id[t]][a[u]]--;
if(num[id[t]][a[u]]==0)
st[id[t]].insert(a[u]);
}
}
}
a[u]=x;
}
else
{
scanf("%d",&u);
int ans=0;
if(degree[u]<block)//轻点,暴力寻找
{
for(int i=0;i<pic[u].size();i++)
{
int t=pic[u][i];
if(a[t]<=degree[u])
vis[a[t]]=1;
}
for(int i=0;i<=degree[u];i++)
{
if(!vis[i])
{
ans=i;
break;
}
}
for(int i=0;i<pic[u].size();i++)
{
int t=pic[u][i];
if(a[t]<=degree[u])
vis[a[t]]=0;
}
}
else
ans=*st[id[u]].begin();
printf("%d\n",ans);
}
}
}
return 0;
}