题目大意:
给定一棵树,每次询问给定一些点,一个点会属于离他最近的给定点,问每个给定点有多少点属于他。
题解:
首先建立一棵虚树,求出虚树上每个点属于哪个点。
然后考虑一条边x->y,若x,y同属一个点,直接更新答案。
否则对于倍增出分界点,更新答案。
#include<cstdio>
#include<algorithm>
using namespace std;
int cnt,n,root,top,ti,sz[600005],dfn[600005],f[600005][20],last[600005],ed[600005],dep[600005],mark[600005],dis[600005],belong[600005],ans[600005],a[600005],q[600005],stack[600005];
struct node{
int to,next,val;
}e[1000005];
void add(int a,int b,int c){
e[++cnt].to=b;
e[cnt].next=last[a];
e[cnt].val=c;
last[a]=cnt;
}
bool cmp(int x,int y){
return dfn[x]<dfn[y];
}
int check(int x,int y){
return dfn[x]<dfn[y] && dfn[y]<=ed[x];
}
void dfs(int x,int fa){ // 预处理
sz[x]=1;
dfn[x]=++ti;
f[x][0]=fa;
for (int j=0; j<19; j++)
f[x][j+1]=f[f[x][j]][j];
for (int i=last[x]; i; i=e[i].next){
int V=e[i].to;
if (V==fa) continue;
dep[V]=dep[x]+1;
dfs(V,x);
sz[x]+=sz[V];
}
ed[x]=ti;
}
void calc1(int x,int fa){ // 求子树内距离每个点最近的点是谁 -> belong[x] 距离 -> dis[x]
// printf("find:%d %d\n",x,fa);
if (mark[x]){
dis[x]=0;
belong[x]=x;
}
else dis[x]=1e9;
for (int i=last[x]; i; i=e[i].next){
int V=e[i].to;
if (V==fa) continue;
calc1(V,x);
if (dis[V]+e[i].val<dis[x] || dis[V]+e[i].val==dis[x] && belong[V]<belong[x]){
belong[x]=belong[V];
dis[x]=dis[V]+e[i].val;
}
}
}
void calc2(int x,int fa,int pre,int preval){ // 求每个点最近的点是谁 -> belong[x] 距离 -> dis[x]
if (preval<dis[x] || preval==dis[x] && pre<belong[x]){
belong[x]=pre;
dis[x]=preval;
}
for (int i=last[x]; i; i=e[i].next){
int V=e[i].to;
if (V==fa) continue;
calc2(V,x,belong[x],dis[x]+e[i].val);
}
}
void pre(){
calc1(root,0);
calc2(root,0,0,1e9);
}
int lca(int x,int y){
if (dep[x]<dep[y]) swap(x,y);
for (int i=19; i>=0; i--){
int to=f[x][i];
if (dep[to]>=dep[y]) x=to;
}
if (x==y) return x;
for (int i=19; i>=0; i--)
if (f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i];
return f[x][0];
}
int query_Dis(int x,int y){ // x为 y 的祖先
return dep[y]-dep[x];
}
int query_dis(int x,int y){ // x->y 在原树上的距离 x不为 y的祖先
int LCA=lca(x,y);
return query_Dis(LCA,x)+query_Dis(LCA,y);
}
int query(int x,int y){ // x->y 在原树上的最后一个点
for (int i=19; i>=0; i--){
int to=f[x][i];
if (dep[to]>dep[y]) x=to;
}
return x;
}
int query_fen(int x,int y){ // x->y 的分界点 z属于x
int Disx=dis[x],Disy=dis[y];
int fx=x,fy=y;
for (int i=19; i>=0; i--) {
int to=f[x][i];
if (dep[to]>dep[y]){
if (Disx+query_Dis(to,fx)<Disy+query_Dis(fy,to) ||
Disx+query_Dis(to,fx)==Disy+query_Dis(fy,to) && belong[fx]<belong[fy]){
x=to;
}
}
}
return x;
}
void solved(int x,int y,int z){ // x->y 统计边上的答案
int fx=belong[x],fy=belong[y];
if (belong[x]==belong[y]){ // 统计答案
ans[belong[x]]+=sz[z]-sz[x];
}
else{ // 倍增找分界点
int z1=query_fen(x,y); // z1属于x
ans[belong[x]]+=sz[z1]-sz[x];
ans[belong[y]]+=sz[z]-sz[z1];
}
}
void solve(int x,int fa){ // 统计点上的答案
int ans1=sz[x];
if (x==root) ans1=n;
for (int i=last[x]; i; i=e[i].next){
int V=e[i].to;
if (V==fa) continue;
int z=query(V,x); // V->x 在原树上的最后一个点
ans1-=sz[z];
solved(V,x,z);
solve(V,fa);
}
ans[belong[x]]+=ans1;
}
int main(){
scanf("%d",&n);
for (int i=1; i<n; i++){
int x,y;
scanf("%d%d",&x,&y);
add(x,y,0);
add(y,x,0);
}
dep[1]=1;
dfs(1,0);
cnt=0;
for (int i=1; i<=n; i++) last[i]=0;
int T;
scanf("%d",&T);
for (int i=1; i<=n; i++) dis[i]=1e9;
while (T--){
int n;
scanf("%d",&n);
for (int i=1; i<=n; i++){
scanf("%d",&a[i]);
q[i]=a[i];
}
sort(a+1,a+n+1,cmp);
int len=n;
for (int i=1; i<n; i++)
a[++len]=lca(a[i],a[i+1]);
sort(a+1,a+len+1,cmp);
len=unique(a+1,a+len+1)-a-1;
top=0;
for (int i=1; i<=len; i++){
while (top>0 && !check(stack[top],a[i])) top--;
if (!stack[top]) root=a[i];
else {
int s=stack[top],t=a[i];
add(s,t,query_dis(s,t));
}
stack[++top]=a[i];
}
for (int i=1; i<=n; i++) mark[q[i]]=1;
pre();
solve(root,0);
for (int i=1; i<=n; i++) mark[q[i]]=0;
for (int i=1; i<=n; i++)
printf("%d ",ans[q[i]]);
printf("\n");
cnt=0;
for (int i=1; i<=len; i++) last[a[i]]=0;
for (int i=1; i<=n; i++) ans[q[i]]=0;
for (int i=1; i<=len; i++) dis[a[i]]=1e9,belong[a[i]]=1e9;
}
return 0;
}