传送
题意
一张 \(n\) 个点 \(m\) 条边的无向图 \(G = (V,E)\) .
每个点有点权 \(A_i\).
对于每个点 \(u\), 定义集合 \(S_u = \{ A_v | (u,v) \in E \}.\)
定义 \(\rm MEX_u\) 为集合 \(S_u\) 中不存在的最小非负整数.
\(q\) 个指令,
1 u x
: 把 \(A_u\) 改为 \(x\).2 u
: 询问 \(\rm MEX_u\).
数据范围
$ 1 \le n \le 10^5,\ 1 \le m \le 10^5,\ 1 \le q \le 10^5,\ 0 \le A_i \le 10^9.$
思路
总
总思路 : 根号分治.
前置知识 : 树状数组二分.
树状数组求 \(\rm MEX\)
首先需要知道, 对于 \(\rm MEX_u\), 我们可以用树状数组 \(O(\log n)\) 的求出.
为了方便描述以及便于树状数组上求解, 我们把所有点权 \(+1\), 并把 \(\rm MEX\) 重定义为集合中 最小的正整数.
求 \(\rm MEX\), 朴素的思路是把集合中的数放进桶里, 然后找桶中的第一个空位. 这个思路也可以描述为 : 找到第一个满足前缀和 \(sum_i \not = i\) 的位置 \(i\).
而前缀和 \(sum_i\) 我们可以用树状数组维护, 然后再在树状数组上二分查找答案就行了.
树状数组二分的思路和线段树二分差不多, 都是一级一级往下找.
具体来说, 初始时把 \(l\) 设为 0, \(r\) 设为二分范围的最大值, 每次 \(mid\) 的值是 \(l\) 加上 \(r-l\) 内最大的 \(2\) 的若干次幂. 因为树状数组中每个节点的 "管理范围" 都是 \(2\) 的若干次幂, 所以按照上述取值方法可以保证每次 \(mid\) 都严格落在 树状数组下一层级中 一个点的 "管理范围" 的最右端, 保证了二分过程中 一级一级 往下找. 时间复杂度为 \(O(\log n)\).
根号分治
由于 \(m \le 10^5\) ($ \sqrt{10^5} \approx 317$), 所以度数大于等于 \(317\) 的点不会超过 \(317\) 个. 我们把这些节点称作大节点, 其他点称作小节点.
对于所有小节点, 我们可以直接暴力遍历它相连的所有节点, 求出 \(\rm MEX\), 单次的时间复杂度为 \(O(\sqrt{n})\).
而对于所有大节点, 我们考虑用树状数组来求出 \(\rm MEX\).
具体来说, 在修改节点 \(u\) 的权值时, 我们遍历它相连的所有大节点 \(x\), 在 \(x\) 的(权值)树状数组上进行相应的修改. 由于大节点的个数不会超过 \(\sqrt{n}\), 所以时间复杂度为 \(O(\sqrt{n} \log{n})\).
然后因为一个点的 \(\rm MEX\) 不会超过它的度数, 所以树状数组的大小只用开到 \(n\) 就行了.
最后总时间复杂度为 \(O(q\sqrt{n}\log{n})\).
代码
P.S. 这份代码比赛的时候过了, 但现在在 HDU 上过不了, 可能是比赛时评测资源比较充足.
#include
using namespace std;
#define pb push_back
#define sz(x) (int)(x).size()
const int _=400+7;
const int __=1e5+7;
int n,m,sq,num[__],val[__],de[__],id[__],sz[_],b[_][__],c[_][__],cnt,q[_];
vector to[__][2];
struct edge{
int u,v;
}e[__];
int gi(){
int x=0; char c=getchar();
while(!isdigit(c)) c=getchar();
while(isdigit(c)) x=(x<<3)+(x<<1)+c-'0',c=getchar();
return x;
}
void Pre(){
num[2]=1; for(int i=3;i<=1e5;i++) num[i]= i>(num[i-1]<<1) ?(num[i-1]<<1) :num[i-1];
}
void Add(int u,int x,int v){
for(int i=x;i<=sz[id[u]];i+=i&(-i))
c[id[u]][i]+=v;
}
void Init(){
n=gi(),m=gi(),sq=sqrt(n);
for(int i=1;i<=n;i++){ val[i]=gi(); ++val[i]; }
for(int i=1;i<=m;i++){
e[i]={gi(),gi()};
++de[e[i].u],++de[e[i].v];
}
for(int i=1;i<=n;i++)
if(de[i]>=sq){ id[i]=++cnt; sz[id[i]]=de[i]; }
for(int i=1;i<=m;i++){
to[e[i].u][0].pb(e[i].v);
to[e[i].v][0].pb(e[i].u);
if(id[e[i].u]) to[e[i].v][1].pb(e[i].u);
if(id[e[i].v]) to[e[i].u][1].pb(e[i].v);
}
for(int i=1;i<=n;i++)
for(auto j:to[i][1])
if(val[i]<=de[j]){
if(!b[id[j]][val[i]]) Add(j,val[i],1);
++b[id[j]][val[i]];
}
}
void Modify(int u,int x){
for(auto v:to[u][1]){
if(val[u]<=de[v]){
--b[id[v]][val[u]];
if(!b[id[v]][val[u]]) Add(v,val[u],-1);
}
if(x<=de[v]){
if(!b[id[v]][x]) Add(v,x,1);
++b[id[v]][x];
}
}
val[u]=x;
}
void Calc(int u){
if(!id[u]){
int x=1;
for(auto v:to[u][0])
if(val[v]<=de[u]){
b[0][val[v]]=1;
q[++q[0]]=val[v];
while(b[0][x]) ++x;
}
printf("%d\n",x-1);
for(int i=1;i<=q[0];i++) b[0][q[i]]=0;
q[0]=0;
}
else{
int l=0,r=sz[id[u]],x=r;
while(l