虚树
考虑这样一类问题:有多次询问,每次询问要规定一些关键点,根据这些关键点提出一些问题,所有询问的关键点总数于整个图中的点数同阶。
如果每次询问都暴力地跑整个树,那么肯定为
P3233世界树
首先一眼能看出需要用虚树,但是难点在于如何DP。
对于每个点,求出管辖它的点
对于非虚树上的点,需要进行分类讨论。
-
对于一个虚树点
,如果它在原树上某个儿子 的整个子树都没有管辖点,那么 整个子树都由 管辖。 -
对于虚树上的一条边,这条边相当于原树上的一条链。这条链的一部分点由
管辖,一部分由 管辖。利用倍增找到分界点即可。
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int L=3e5+5;
int n,m,k,u,v,w,cnt,h[L],stk[L],dfn[L],r,tot,head[L],ver[L*2],nxt[L*2],d[L],lg[L],fa[L][25];
int ct,cn[L],ch[L],cv[L],dp[L],c[L],siz[L],ans[L],b[L];bool ok[L];
void add(int x,int y){
nxt[++tot]=head[x];head[x]=tot;ver[tot]=y;
}
void add2(int x,int y){
cn[++ct]=ch[x];ch[x]=ct;cv[ct]=y;
}
void dfs(int x,int father){
dfn[x]=++cnt;
siz[x]=1;
d[x]=d[father]+1;
fa[x][0]=father;
for(int i=1;i<=lg[d[x]];++i)
fa[x][i]=fa[fa[x][i-1]][i-1];
for(int i=head[x];i;i=nxt[i])
if(ver[i]!=father){
dfs(ver[i],x);
siz[x]+=siz[ver[i]];
}
}
int lca(int x,int y){
if(d[x]<d[y])
swap(x,y);
while(d[x]>d[y])
x=fa[x][lg[d[x]-d[y]]];
if(x==y)
return x;
for(int i=20;i>=0;--i)
if(fa[x][i]!=fa[y][i])
x=fa[x][i],y=fa[y][i];
return fa[x][0];
}
bool cmp(int x,int y){
return dfn[x]<dfn[y];
}
void calc(int u,int v){//处理非虚树的点
int q=v;
for(int i=20;i>=0;--i)
if(d[fa[q][i]]>d[u]){//倍增找分界点,fa[q][i]是u的儿子
int len1=d[v]-d[fa[q][i]]+dp[v],len2=d[fa[q][i]]-d[u]+dp[u];
//len1表示v的管辖点到fa[q][i]距离,len2表示u的管辖点到fa[q][i]的距离
if(len1<len2||(len1==len2&&c[v]<c[u]))
q=fa[q][i];//分界点在上边,q需要往上跳
}//q及其子树都归v管辖
ans[c[v]]+=siz[q]-siz[v];ans[c[u]]-=siz[q];
}
void dfs1(int x,int father){//用儿子更新父亲
dp[x]=1e9;
for(int i=ch[x];i;i=cn[i])
if(cv[i]!=father){
dfs1(cv[i],x);
if(dp[cv[i]]+d[cv[i]]-d[x]<dp[x])//x的儿子到其管辖点的距离 + x到儿子的距离 < x到其管辖点的距离
dp[x]=dp[cv[i]]+d[cv[i]]-d[x],c[x]=c[cv[i]];//此时x的管辖点变为x的儿子的管辖点
else if(dp[cv[i]]+d[cv[i]]-d[x]==dp[x])
c[x]=min(c[x],c[cv[i]]);//距离相等时,选择编号小的
}
if(ok[x]) dp[x]=0,c[x]=x;//x为管辖点
}
void dfs2(int x,int father){//用父亲更新儿子
for(int i=ch[x];i;i=cn[i])
if(cv[i]!=father){
if(dp[x]+d[cv[i]]-d[x]<dp[cv[i]])//x到其管辖点的距离 + x到儿子的距离 < x的儿子到其管辖点的距离
dp[cv[i]]=dp[x]+d[cv[i]]-d[x],c[cv[i]]=c[x];//此时x的儿子的管辖点变为x的管辖点
else if(dp[x]+d[cv[i]]-d[x]==dp[cv[i]])
c[cv[i]]=min(c[cv[i]],c[x]);//距离相等时,选择编号小的
calc(x,cv[i]);dfs2(cv[i],x);
}
ans[c[x]]+=siz[x];
ch[x]=0;ok[x]=0;
}
void work(){
stk[r=1]=1;ct=0;
sort(h+1,h+k+1,cmp);
for(int i=1;i<=k;++i)
if(h[i]!=1){
int tmp=lca(stk[r],h[i]);
if(tmp!=stk[r]){
while(dfn[tmp]<dfn[stk[r-1]])
add2(stk[r-1],stk[r]),add2(stk[r],stk[r-1]),--r;
if(dfn[tmp]>dfn[stk[r-1]])
add2(tmp,stk[r]),add2(stk[r],tmp),stk[r]=tmp;
else
add2(tmp,stk[r]),add2(stk[r],tmp),--r;
}
stk[++r]=h[i];
}
for(int i=1;i<r;++i)
add2(stk[i],stk[i+1]),add2(stk[i+1],stk[i]);
dfs1(1,0);dfs2(1,0);
for(int i=1;i<=k;++i)
printf("%d ",ans[b[i]]),ans[b[i]]=0;
printf("\n");
return ;
}
int main(){
scanf("%d",&n);
for(int i=1;i<n;++i){
scanf("%d%d",&u,&v);
add(u,v);add(v,u);
}
for(int i=1;i<=n;++i)
lg[i]=lg[i-1]+((1<<(lg[i-1]+1))==i);
dfs(1,0);
scanf("%d",&m);
for(int i=1;i<=m;++i){
scanf("%d",&k);
for(int i=1;i<=k;++i)
scanf("%d",&h[i]),b[i]=h[i],ok[h[i]]=1;
work();
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探