342E. Xenia and Tree(sqrt-optimization+多源点BFS+倍增LCA)
给出一棵树,一开始1号点是红色的,有2种操作:
(1)把一个节点改成红色
(2)每次询问一个红色节点,输出和它最接近的红色节点
一个全新的套路:对询问分块+多源\(BFS\)
用一个\(Vector\)保存当前还未被修改的红色节点
将修改操作分块后,对每一块的操作先全部修改一遍,然后多源\(BFS\)更新每个蓝色节点和距离它最近红色节点的距离。
然后对每个询问,先确定它距离已经修改的红色节点的最近距离,然后枚举还没修改的红色节点,用倍增\(LCA\)查询距离并更新之前保存的最近距离。
因为分块的存在,可以保证\(Vector\)的大小始终是\(\sqrt n\)。
总体时间复杂度\(n\sqrt nlogn\)。
#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+100;
int n,m;
vector<int> g[maxn];
int is[maxn];//表示是否是红色节点
int h[maxn],father[30][maxn];
int d[maxn];//表示距离当前最近红色节点的距离
vector<int> v;//表示还没有被染色的红色节点
void dfs (int x) {
for (int y:g[x]) {
if (y==father[0][x]) continue;
father[0][y]=x;
h[y]=h[x]+1;
dfs(y);
}
}
int lca (int x,int y) {
if (h[x]<h[y]) swap(x,y);
for (int i=20;i>=0;i--) if (h[x]-h[y]>>i) x=father[i][x];
if (x==y) return x;
for (int i=20;i>=0;i--) {
if (father[i][x]!=father[i][y]) {
x=father[i][x];
y=father[i][y];
}
}
return father[0][x];
}
int getDis (int x,int y) {
return h[x]+h[y]-2*h[lca(x,y)];
}
int vis[maxn];
void bfs () {
queue<int> q;
for (int i=1;i<=n;i++) vis[i]=0;
for (int i=1;i<=n;i++) if (is[i]) q.push(i),vis[i]=1,d[i]=0;
while (q.size()) {
int x=q.front();
q.pop();
for (int y:g[x]) {
if (vis[y]) continue;
d[y]=d[x]+1;
vis[y]=1;
q.push(y);
}
}
}
int main () {
scanf("%d%d",&n,&m);
for (int i=1;i<n;i++) {
int x,y;
scanf("%d%d",&x,&y);
g[x].push_back(y);
g[y].push_back(x);
}
is[1]=1;
dfs(1);
bfs();
for (int i=1;i<=20;i++) for (int j=1;j<=n;j++) father[i][j]=father[i-1][father[i-1][j]];
int Size=pow(4.5*m*log(n)/log(2.0),1.0/3)+1;
for (int i=1;i<=m;i++) {
int x,y;
scanf("%d%d",&x,&y);
if (x==1) {
v.push_back(y);
int vs=v.size();
if (vs==Size) {
//一个块满
for (int j=0;j<v.size();j++) is[v[j]]=1;
bfs();
v.clear();
}
}
else {
int ans=d[y];
for (int j=0;j<v.size();j++) ans=min(ans,getDis(y,v[j]));
printf("%d\n",ans);
}
}
}