題意
一張 \(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})\).
代碼
#include<bits/stdc++.h>
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<int> 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<r-1){
int mid=l+num[r-l];
if(c[id[u]][mid]<mid-l) x=r=mid;
else l=mid;
}
printf("%d\n",x-1);
}
}
void Run(){
int Q=gi(),ty,u,x;
while(Q--){
ty=gi(),u=gi();
if(ty==1){ x=gi(); Modify(u,x+1); }
else Calc(u);
}
for(int i=1;i<=n;i++){
to[i][0].clear(),to[i][1].clear();
de[i]=1; id[i]=0;
}
for(int i=1;i<=cnt;i++)
for(int j=1;j<=sz[i];j++)
b[i][j]=c[i][j]=0;
cnt=0;
}
int main(){
#ifndef ONLINE_JUDGE
freopen("1.in","r",stdin);
freopen("x.out","w",stdout);
#endif
int T=gi();
Pre();
while(T--){
Init();
Run();
}
return 0;
}