题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=2586
题目大意:在一个无向树上,求一条链权和。
解题思路:
0
|
1
/ \
2 3
设dist[i]为i到根0的链和,求法(Dfs过程中dist[v]=dist[u]+e[i].w)
对于树中任意两点形成的链,可以通过LCA最近公共祖先剖分。
比如2->3,就可以经过LCA点1: 2->1->3
链和=dist[u]+dist[v]-2*dist[LCA[u,v]]
(0-1-2)+(0-1-3)-2*(0-1)=(2-1-3),有点容斥原理的味道。
LCA比较快的是Tarjan离线法,把全部query也做成一个无向树,离线处理。
过程分为两个stage,stage 1对连接树处理,stage 2对query树处理。
两个stage可以颠倒。正写法 倒写法。在Tarjan(u)中,并查集find(v),可以获得LCA。
LCA存储比较头疼,由于LCA是双向共享的。可以建个ancestor数组,索引是查询序号。
也可以直接存在query树的链式前向星中。
本题双向建一个无向树,任意选择一个起点作为root做LCA都可以。
#include "cstdio" #include "cstring" #define maxn 40005 #define maxm 205 int head[maxn],qhead[maxn],dist[maxn],tot1,tot2,f[maxn],vis[maxn],ancestor[maxn]; struct Edge { int to,next,w; }e[maxn*2]; struct Query { int from,to,next,idx; }q[maxn*2]; void addedge(int u,int v,int w) { e[tot1].to=v; e[tot1].w=w; e[tot1].next=head[u]; head[u]=tot1++; } void addquery(int u,int v,int idx) { q[tot2].from=u; q[tot2].to=v; q[tot2].next=qhead[u]; q[tot2].idx=idx; qhead[u]=tot2++; } int find(int x) {return x!=f[x]?f[x]=find(f[x]):x;} void Union(int u,int v) { u=find(u),v=find(v); if(u!=v) f[v]=u; } void LCA(int u) { vis[u]=true; f[u]=u; for(int i=head[u];i!=-1;i=e[i].next) { int v=e[i].to,w=e[i].w; if(!vis[v]) { dist[v]=dist[u]+w; LCA(v); Union(u,v); } } for(int i=qhead[u];i!=-1;i=q[i].next) { int v=q[i].to; if(vis[v]) ancestor[q[i].idx]=find(v); //or storage e[i].lca=e[i^1].lca=find(v) } } int main() { //freopen("in.txt","r",stdin); int T,n,m,u,v,c; scanf("%d",&T); while(T--) { tot1=tot2=0; memset(head,-1,sizeof(head)); memset(qhead,-1,sizeof(qhead)); memset(vis,0,sizeof(vis)); dist[1]=0; scanf("%d%d",&n,&m); for(int i=0;i<n-1;i++) { scanf("%d%d%d",&u,&v,&c); addedge(u,v,c); addedge(v,u,c); } for(int i=0;i<m;i++) { scanf("%d%d",&u,&v); addquery(u,v,i); addquery(v,u,i); } LCA(1); for(int i=0;i<tot2;i=i+2) { int u=q[i].from,v=q[i].to,idx=q[i].idx; printf("%d\n",dist[u]+dist[v]-2*dist[ancestor[idx]]); } } }
时间: 2024-10-10 12:38:01