P3233 [HNOI2014]世界树
题目
首先考虑暴力:
我们要计算每一个点离它最近的议事点,这个可以通过两遍
1、计算当前点
2、此时根节点就保存了它需要的答案,我们用父亲节点去更新儿子,更新完后再递归
inline void dfs1(int p,int fa)
{
dp[p]=INF;//dp表示最短距离,g表示编号
for(register int i=fir[p];i;i=e[i].nxt)
{
int q=e[i].to;
if(q==fa) continue;
dfs1(q,p);//用儿子更新自己
if(dp[q]+1<dp[p]) dp[p]=dp[q]+1,g[p]=g[q];
else if(dp[q]+1==dp[p]) g[p]=min(g[p],g[q]);
}
if(vis[p]) dp[p]=0,g[p]=p;
vis[p]=0;
}
inline void dfs2(int p,int fa)
{
for(register int i=fir[p];i;i=e[i].nxt)
{
int q=e[i].to;
if(q==fa) continue;//用自己更新儿子
if(dp[p]+1<dp[q]) dp[q]=dp[p]+1,g[q]=g[p];
else if(dp[p]+1==dp[q]) g[q]=min(g[q],g[p]);
dfs2(q,p);
}
}
看到有
对于虚树上的点(议事点和它们的
那怎么计算非虚树上的节点呢?
第一种情况:
原树上一个点
第二种情况:
原树上一条链,两端都是议事点,这一条链各一半属于两个议事点
对应到虚树上就是:
1、一个虚树点
2、对于虚树上的一条边,它两端
具体实现:
1、对于虚树上的一个点
2、原树上的链。虚树上的两个点
举个栗子(样例第三个再加一个点):
我们倍增找到
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <algorithm>
using namespace std;
const int N=3e5+10,INF=1e9+10;
int n,m,top,cnt;
int dep[N],f[N][20],dfn[N],size[N],lg2[N],sta[N],pnt[N],vis[N],g[N],dp[N],ans[N],tmp[N];
struct graph
{
int tot;
int fir[N],to[2*N],nxt[2*N];
graph(){ tot=0; memset(fir,0,sizeof(fir)); }
inline void add(int x,int y)
{
to[++tot]=y; nxt[tot]=fir[x]; fir[x]=tot;
to[++tot]=x; nxt[tot]=fir[y]; fir[y]=tot;
}
}e1,e2;
inline void dfs(int p)//预处理dfn,dep,size
{
dfn[p]=++cnt,size[p]=1;
for(register int i=e1.fir[p];i;i=e1.nxt[i])
{
int q=e1.to[i];
if(q==f[p][0]) continue;
dep[q]=dep[p]+1,f[q][0]=p;
for(register int j=1;j<=lg2[dep[q]]+1;j++)
f[q][j]=f[f[q][j-1]][j-1];
dfs(q);
size[p]+=size[q];
}
}
inline int get_lca(int x,int y)//找lca
{
if(dep[x]<dep[y]) swap(x,y);
for(register int i=lg2[dep[x]];i>=0;i--)
if(dep[f[x][i]]>=dep[y]) x=f[x][i];
if(x==y) return x;
for(register int i=lg2[dep[x]];i>=0;i--)
if(f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i];
return f[x][0];
}
inline bool cmp(int a,int b)
{
return dfn[a]<dfn[b];
}
inline void build(int p)//建立虚树
{
if(top==0){ sta[top=1]=p; return; }
int lca=get_lca(sta[top],p);
while(top>1&&dep[lca]<dep[sta[top-1]]) e2.add(sta[top-1],sta[top]),top--;
if(dep[lca]<dep[sta[top]]) e2.add(lca,sta[top--]);
if(top==0||sta[top]!=lca) sta[++top]=lca;
sta[++top]=p;
}
inline void cal(int x,int y)
{
int p=y,q=y;
for(register int i=lg2[dep[p]];i>=0;i--)
if(dep[f[p][i]]>dep[x]) p=f[p][i];
ans[g[x]]-=size[p];//跳到y在原树上对应的x的儿子
for(register int i=lg2[dep[q]];i>=0;i--)
{
int llen=dep[y]-dep[f[q][i]]+dp[y],rlen=dep[f[q][i]]-dep[x]+dp[x];
if(dep[f[q][i]]>dep[x]&&(llen<rlen||(llen==rlen&&g[y]<g[x]))) q=f[q][i];//倍增找到分割点
}
ans[g[y]]+=size[q]-size[y],ans[g[x]]+=size[p]-size[q];//注意这里要加的是size,因为虚树路径上会有子树
}
inline void dfs1(int p,int fa)
{
dp[p]=INF;
for(register int i=e2.fir[p];i;i=e2.nxt[i])
{
int q=e2.to[i];
if(q==fa) continue;
dfs1(q,p);
int dis=dep[q]-dep[p];//注意这里,虚树上的节点并不是连续的
if(dp[q]+dis<dp[p]) dp[p]=dp[q]+dis,g[p]=g[q];
else if(dp[q]+dis==dp[p]) g[p]=min(g[p],g[q]);
}
if(vis[p]) dp[p]=0,g[p]=p;
}
inline void dfs2(int p,int fa)
{
for(register int i=e2.fir[p];i;i=e2.nxt[i])
{
int q=e2.to[i];
if(q==fa) continue;
int dis=dep[q]-dep[p];
if(dp[p]+dis<dp[q]) dp[q]=dp[p]+dis,g[q]=g[p];
else if(dp[p]+dis==dp[q]) g[q]=min(g[q],g[p]);
cal(p,q);
dfs2(q,p);
}
ans[g[p]]+=size[p];//注意这里,还要加上自己
vis[p]=e2.fir[p]=0;
}
int main()
{
lg2[1]=0;
for(register int i=1;i<=3e5;i++)
lg2[i]=lg2[i>>1]+1;
int a,b,T;
scanf("%d",&n);
for(register int i=1;i<n;i++)
{
scanf("%d%d",&a,&b);
e1.add(a,b);
}
dep[1]=1,dfs(1);
scanf("%d",&T);
while(T--)
{
int flag=1;
top=e2.tot=0;
scanf("%d",&m);
for(register int i=1;i<=m;i++)
scanf("%d",&pnt[i]),vis[pnt[i]]=1,ans[pnt[i]]=0;
if(!vis[1]) pnt[++m]=1,flag=0;
for(register int i=1;i<=m;i++)
tmp[i]=pnt[i];
sort(pnt+1,pnt+m+1,cmp);
for(register int i=1;i<=m;i++)
build(pnt[i]);
if(top) while(--top) e2.add(sta[top],sta[top+1]);
dfs1(1,0),dfs2(1,0);
for(register int i=1;i<=m;i++)
if(tmp[i]!=1||flag) printf("%d ",ans[tmp[i]]);//注意判断1
printf("\n");
}
return 0;
}