给定一颗有 n n n个点的树,进行 q q q次操作。
操作 1 1 1:将第 x x x个点染为黑色。
操作 2 2 2:询问 x x x到所有黑色点的路径上的编号最小的点。
操作强制在线。
先给出做法,在给出证明。
以第一个黑点为根,做一次 d f s dfs dfs,处理出 a i a_i ai表示 i i i到根的路径上编号最小的点。
询问操作的答案就是当前所有激活黑点的 a i a_i ai与 a x a_x ax取最小。
证明:
设答案点所在路径的两端为 u , v u,v u,v。
无论当前路径是否经过根节点, a n s = min ( a u , a v ) ans= \min(a_u,a_v) ans=min(au,av)。
因为两个点之间的路径可以拆分成 ( u , l c a ( u , v ) ) (u,lca(u,v)) (u,lca(u,v)), ( v , l c a ( u , v ) ) (v,lca(u,v)) (v,lca(u,v))。
可以发现这两条路径都会被他们到根的路径包含,又因为每个黑点或询问的点与根的路径都会对答案做出贡献,所以就可以按上面的方法做。
具体实现参考代码。
#include
using namespace std;
int n,q,a[1000000+10],f[1000000+10],root=0,ans=INT_MAX,lastans=0;
vector<int> son[1000000+10];
int read()
{
int s=0,w=1;
char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-')
w=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9')
s=s*10+(ch-'0'),ch=getchar();
return s*w;
}
void dfs(int u,int fa)
{
f[u]=min(u,f[fa]);
for(auto v:son[u])
{
if(v==fa)
continue;
dfs(v,u);
}
}
int main()
{
n=read(),q=read();
for(int i=1;i<=n-1;++i)
{
int u=read(),v=read();
son[u].push_back(v);
son[v].push_back(u);
}
for(int i=1;i<=n;++i)
f[i]=INT_MAX;
while(q--)
{
int opt=read(),u=read();
u=(u+lastans)%n+1;
if(opt==1)
{
a[u]=1;
if(!root)
{
root=u;
f[root]=root;
dfs(root,root);
}
ans=min(ans,f[u]);
}
else
{
lastans=min(ans,f[u]);
printf("%d\n",lastans);
}
}
return 0;
}