题解:CF1904E Tree Queries
思路:
离线 + 分块。
观察一下性质发现,一个点不能经过等价于将树分成两部分。
最后我们要确定 \(x\) 所在联通块的范围。
对于不能经过的点 \(a_i\),我们先找出在 \(x\) 到 \(1\) 路径上深度最深的 \(a_i\),记为 \(y\),如果没有 \(y\) 默认为 \(1\)。这一步可以树上倍增来求。
\(x\) 所在联通块的就是 \(y\) 这棵子树的子集。
对于剩下的 \(a_i\),如果 \(a_i\) 在 \(y\) 这棵子树内,那么 \(a_i\) 这棵子树不可达,无效。
把树拍平,按照 dfs 序对应到区间上,\(y\) 这棵子树对应的区间 \([l,r]\)。\(y\) 子树内不可达的 \(a_i\) 对应的子树区间为 \([l_i,r_i]\),设这些区间的并集为 \(S\),那么我们就是要询问以 \(x\) 为根时,对应的区间集合 \([l,r]\setminus S\),即当 \([l,r]\) 是全集的时候,\(S\) 的补集中,到 \(x\) 距离最远的点。
那么如何求到 \(x\) 最远的点?
考虑先求出以 \(1\) 为根时所有点的距离,利用换根思想,对于根从 \(u\rightarrow v\) 时,\(v\) 这棵子树内的点到 \(v\) 的比在原来到 \(u\) 的基础上减少 \(1\),这一步就是子树加减 \(1\) 的操作,而对于其他点,到 \(v\) 的距离比原来到 \(u\) 的距离增加了 \(1\),修改时直接全局加 \(1\) ,子树减 \(2\) 就行了。
时间复杂度为 \(O(n\log n+n\sqrt n)\),如果用其他数据结构例如线段树可以 \(O(n\log n)\)。
code
const int N=2e5+10,M=450;
struct Query{
int l,r,id;
vector<pair<int,int>>Seg;
};
vector<Query>S[N];
int Be[N],L[M],R[M],ma[M],add[M];
int n,m,dep[N],rt=1;
int dfn[N],ID[N],siz[N],t=0;
int fa[N][21],Ans[N];
vector<int>G[N];
void dfs(int now,int from)
{
ID[dfn[now]=++t]=now;siz[now]=1;
for(int i=1;(1<<i)<=dep[now];i++)
fa[now][i]=fa[fa[now][i-1]][i-1];
for(int to:G[now])
{
if(to==from) continue;
dep[to]=dep[now]+1,fa[to][0]=now;
dfs(to,now);
siz[now]+=siz[to];
}
return;
}
int LCA(int x,int y)
{
if(dep[x]<dep[y]) swap(x,y);
for(int i=20;~i;i--)
if((1<<i)<=dep[x]-dep[y]) x=fa[x][i];
if(x==y) return x;
for(int i=20;~i;i--)
if(fa[x][i]^fa[y][i]) x=fa[x][i],y=fa[y][i];
return fa[x][0];
}
#define fi(x) x.first
#define se(x) x.second
void build()
{
int len=sqrt(n),t=n/len+bool(n%len);
for(int i=1;i<=n;i++)
{
Be[i]=(i-1)/len+1;
if(!L[Be[i]]) L[Be[i]]=i;
R[Be[i]]=i;
}
for(int i=1;i<=t;i++)
for(int j=L[i];j<=R[i];j++) ma[i]=max(ma[i],dep[ID[j]]);
return;
}
inline int query(int l,int r)
{
if(l>r) return -1;
int res=0;
if(Be[l]==Be[r]){
for(int i=l;i<=r;i++)
res=max(res,dep[ID[i]]+add[Be[l]]);
}
else{
for(int i=l;i<=R[Be[l]];i++) res=max(res,dep[ID[i]]+add[Be[l]]);
for(int i=L[Be[r]];i<=r;i++) res=max(res,dep[ID[i]]+add[Be[r]]);
for(int i=Be[l]+1;i<=Be[r]-1;i++) res=max(res,ma[i]+add[i]);
}
return res;
}
inline void rebuild(int now)
{
ma[now]=0;
for(int i=L[now];i<=R[now];i++)
ma[now]=max(ma[now],dep[ID[i]]);
return;
}
inline void modify(int l,int r,int val)
{
if(Be[l]==Be[r]){
for(int i=l;i<=r;i++)
dep[ID[i]]+=val;
rebuild(Be[l]);
}
else{
for(int i=l;i<=R[Be[l]];i++) dep[ID[i]]+=val;
for(int i=L[Be[r]];i<=r;i++) dep[ID[i]]+=val;
rebuild(Be[l]);rebuild(Be[r]);
for(int i=Be[l]+1;i<=Be[r]-1;i++) add[i]+=val;
}
return;
}
void calc(int now,int from)
{
for(auto [l,r,id,Seg]:S[now])
{
int res=0;
if(!Seg.size()) res=max(res,query(l,r));
//注意特判 y 子树都合法的情况
else{
sort(Seg.begin(),Seg.end());
int up=Seg.size()-1;
int mi=n+1,ma=-1;
for(auto [pl,pr]:Seg) mi=min(mi,pl),ma=max(ma,pr);
if(mi>l) res=max(res,query(l,mi-1));
if(ma<r) res=max(res,query(ma+1,r));
//两端可能还有合法区间
ma=se(Seg[0]);
for(int i=1;i<=up;i++)
{
if(fi(Seg[i])<=ma) continue;
res=max(res,query(ma+1,fi(Seg[i])-1));
ma=se(Seg[i]);
}
//询问补集
}
Ans[id]=res;
}
for(int to:G[now])
{
if(to==from) continue;
modify(dfn[to],dfn[to]+siz[to]-1,-2),modify(1,n,1);//换根
calc(to,now);
modify(dfn[to],dfn[to]+siz[to]-1,2),modify(1,n,-1);//记得换回来
}
}
int main()
{
read(n,m);
for(int i=1,x,y;i<n;i++)
{
read(x,y);
G[x].push_back(y);
G[y].push_back(x);
}
dfs(rt,0);build();
int x,k;
for(int i=1;i<=m;i++)
{
read(x,k);
vector<int>d;
int up=dfn[rt],w=rt;
for(int j=1,y,z;j<=k;j++)
{
read(y);d.push_back(y);
if(LCA(x,y)==y){
z=x;
for(int p=20;~p;p--)
if(fa[z][p]&&dep[fa[z][p]]>dep[y]) z=fa[z][p];
if(dfn[z]>up) up=dfn[z],w=z;//倍增求 y
}
}
vector<pair<int,int>>c;
for(int to:d)
{
if(to==w) continue;
if(LCA(to,w)==w)
c.push_back({dfn[to],dfn[to]+siz[to]-1});
//把不可达的子树对应区间抠出来
}
S[x].push_back({dfn[w],dfn[w]+siz[w]-1,i,c});
}
calc(rt,0);
for(int i=1;i<=m;i++) write(Ans[i],'\n');
return 0;
}

浙公网安备 33010602011771号