BZOJ 3572: [Hnoi2014]世界树
题目大意:
给定一棵树,每次询问给定一些点,一个点会属于离他最近的给定点,问每个给定点有多少点属于他。
题解:
首先建立一棵虚树,求出虚树上每个点属于哪个点。
然后考虑一条边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; }