#pragma comment(linker, "/STACK:1024000000,1024000000")
#include <cstdio>
#include <cstring>
#include <cmath>
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
const int maxn=40004;
struct node{
int to,w;
node(int a=0,int b=0){to=a;w=b;}
};
vector<node>e[maxn];
int f[maxn],dis[maxn],deep[maxn],p[maxn][20],n;
void dfs(int u,int pre,int t)
{
int i,num;
deep[u]=t;//深度
f[u]=pre;//父节点
num=e[u].size();
for(i=0;i<num;i++)
{
int v=e[u][i].to;
if(v!=pre)
{
dis[v]=dis[u]+e[u][i].w;//距离跟的距离
dfs(v,u,t+1);
}
}
}
void init()
{
//p[i][j]表示i结点的第2^j祖先
int i,j;
for(j=0;(1<<j)<=n;j++)
for(i=1;i<=n;i++)
p[i][j]=-1;
for(i=1;i<=n;i++)p[i][0]=f[i];
for(j=1;(1<<j)<=n;j++)
for(i=1;i<=n;i++)
if(p[i][j-1]!=-1)
p[i][j]=p[p[i][j-1]][j-1];//i的第2^j祖先就是i的第2^(j-1)祖先的第2^(j-1)祖先
}
int lca(int a,int b)//最近公共祖先
{
int i,j;
if(deep[a]<deep[b])swap(a,b);
for(i=0;(1<<i)<=deep[a];i++);
i--;
//使a,b两点的深度相同
for(j=i;j>=0;j--)
if(deep[a]-(1<<j)>=deep[b])
a=p[a][j];
if(a==b)return a;
//倍增法,每次向上进深度2^j,找到最近公共祖先的子结点
for(j=i;j>=0;j--)
{
if(p[a][j]!=-1&&p[a][j]!=p[b][j])
{
a=p[a][j];
b=p[b][j];
}
}
return f[a];
}
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
int m,i,a,b,c,ans;
scanf("%d%d",&n,&m);
for(i=1;i<=n;i++)e[i].clear();
for(i=1;i<n;i++)
{
scanf("%d%d%d",&a,&b,&c);
e[a].push_back(node(b,c));
e[b].push_back(node(a,c));
}
dis[1]=0;
dfs(1,-1,0);//找到各点的深度和各点的父节点以及距离根的距离
init(); //初始各个点的2^j祖先是谁
for(i=0;i<m;i++)
{
scanf("%d%d",&a,&b);
ans=dis[a]+dis[b]-2*dis[lca(a,b)];
printf("%d\n",ans);
}
}
return 0;
}
/*
最近公共祖先lca,在线算法/倍增法,模板题。套别人模板自己敲了遍,现在还要回顾下邻接表,哎。。
用vector,发现爆栈了,汗一个。。
用#pragma comment(linker, "/STACK:1024000000,1024000000") 开个把栈开大点吧。。hdu可以,别的地方就不清楚了
*/
下面是用邻接表存图,经证明栈内存不够主要应该是在线运算太耗了的缘故吧。。
#pragma comment(linker, "/STACK:1024000000,1024000000")
#include <cstdio>
#include <cstring>
#include <cmath>
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
const int maxn=40004;
struct node{
int to,w,next;
}e[maxn*2];
int f[maxn],dis[maxn],deep[maxn],p[maxn][20],n,tt,head[maxn];
void add(int a,int b,int c)
{
e[tt].to=b;
e[tt].w=c;
e[tt].next=head[a];
head[a]=tt++;
}
void dfs(int u,int pre,int t)
{
int i,num;
deep[u]=t;//深度
f[u]=pre;//父节点
for(i=head[u];i!=-1;i=e[i].next)
{
int v=e[i].to;
if(v!=pre)
{
dis[v]=dis[u]+e[i].w;//距离跟的距离
dfs(v,u,t+1);
}
}
}
void init()
{
//p[i][j]表示i结点的第2^j祖先
int i,j;
for(j=0;(1<<j)<=n;j++)
for(i=1;i<=n;i++)
p[i][j]=-1;
for(i=1;i<=n;i++)p[i][0]=f[i];
for(j=1;(1<<j)<=n;j++)
for(i=1;i<=n;i++)
if(p[i][j-1]!=-1)
p[i][j]=p[p[i][j-1]][j-1];//i的第2^j祖先就是i的第2^(j-1)祖先的第2^(j-1)祖先
}
int lca(int a,int b)//最近公共祖先
{
int i,j;
if(deep[a]<deep[b])swap(a,b);
for(i=0;(1<<i)<=deep[a];i++);
i--;
//使a,b两点的深度相同
for(j=i;j>=0;j--)
if(deep[a]-(1<<j)>=deep[b])
a=p[a][j];
if(a==b)return a;
//倍增法,每次向上进深度2^j,找到最近公共祖先的子结点
for(j=i;j>=0;j--)
{
if(p[a][j]!=-1&&p[a][j]!=p[b][j])
{
a=p[a][j];
b=p[b][j];
}
}
return f[a];
}
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
int m,i,a,b,c,ans;
scanf("%d%d",&n,&m);
memset(head,-1,sizeof(head));
tt=0;
for(i=1;i<n;i++)
{
scanf("%d%d%d",&a,&b,&c);
add(a,b,c);
add(b,a,c);
}
dis[1]=0;
dfs(1,-1,0);//找到各点的深度和各点的父节点以及距离根的距离
init(); //初始各个点的2^j祖先是谁
for(i=0;i<m;i++)
{
scanf("%d%d",&a,&b);
ans=dis[a]+dis[b]-2*dis[lca(a,b)];
printf("%d\n",ans);
}
}
return 0;
}
下面是离线算法,我想应该不会栈内存不够啊,为什么还是爆。。
#pragma comment(linker, "/STACK:1024000000,1024000000")
#include <cstdio>
#include <cstring>
#include <cmath>
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
const int maxn=40004;
struct node{
int to,w;
node(int a=0,int b=0){to=a;w=b;}
};
int f[maxn],dis[maxn],n,ans[maxn],vis[maxn];
//f[i]并查集所用,记录前继结点
//dis[i]记录个点到跟结点的距离
//ans记录m个询问的答案
//vis标记查询过了的点
vector<node>e[maxn];//记录树
vector<node>q[maxn];//记录所求最短距离的两点
int find(int x)
{
if(x!=f[x])f[x]=find(f[x]);
return f[x];
}
void lca(int u)
{
int i,j,k,v,c;
for(i=0;i<e[u].size();i++)
{
v=e[u][i].to;
if(vis[v])continue;
vis[v]=1;
dis[v]=dis[u]+e[u][i].w;
lca(v);//深度优先搜索
f[v]=u;
for(j=0;j<q[v].size();j++)
{
c=q[v][j].to;
//如果所求两点中的对应点已知,则必定在同一子树上,由于并查集只记录所在子树。所以find(c),就是最近公共祖先
if(vis[c]&&ans[q[v][j].w]==-1)
{
if(v==c)ans[q[v][j].w]=0;//自成环
else ans[q[v][j].w]=dis[v]+dis[c]-2*dis[find(c)];
}
}
}
}
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
int i,j,k,a,b,c,m,aa,bb;
scanf("%d%d",&n,&m);
for(i=1;i<=n;i++)
{
e[i].clear();
q[i].clear();
ans[i]=-1;
f[i]=i;
vis[i]=0;
}
for(i=1;i<n;i++)
{
scanf("%d%d%d",&a,&b,&c);
e[a].push_back(node(b,c));
e[b].push_back(node(a,c));
}
for(i=1;i<=m;i++)
{
scanf("%d%d",&a,&b);
q[a].push_back(node(b,i));
q[b].push_back(node(a,i));
}
vis[1]=1;
dis[1]=0;
lca(1);
for(i=1;i<=m;i++)
printf("%d\n",ans[i]);
}
return 0;
}
/*
最近公共祖先lca 离线算法/Tarjan算法
方法举例说明:
1
/ \
2 3
/ \
4 5
/ /
7 8
/
9
查询(4,9):到4时,由于vis[9]=0,所以继续;到9后,最近公共祖先就是f[4]=4(回溯的时候记录父节点);
查询(9,8):深度优先搜索,所以到8时才询问;要到8必须回溯到2,在进到8这个子树,所以以记录f[9]=7;f[7]=4;f[4]=2;f[2]=2;所以find(2)=2;
查询(8,3):跟上条相似,必须回溯1才能到3,而find(8)=1就是最近公共祖先;
我们可以发现,对于查询的两点,都要在先查询到的点开始,回溯到最近公共祖先,才查询相对应的点。这也就是离线算法的思路了。。这是我自己理解的。。
下面是专业说明,反正我是没看懂。。
利用并查集优越的时空复杂度,我们可以实现LCA问题的O(n+Q)算法,这里Q表示询问的次数。
Tarjan算法基于深度优先搜索的框架,对于新搜索到 的一个结点,首先创建由这个结点构成的集合,
再对当前结点的每一个子树进行搜索,每搜索完一棵子树,则可确定子树内的LCA询问都已解决。
其他的LCA询问的结果必然在这个子树之外,这时把子树所形成的集合与当前结点的集合合并,并将当前结点设为这个集合的祖先。
之后继续搜索下一棵子树,直到当前结点的所 有子树搜索完。这时把当前结点也设为已被检查过的,
同时可以处理有关当前结点的LCA询问,如果有一个从当前结点到结点v的询问,
且v已被检查过,则由于 进行的是深度优先搜索,当前结点与v的最近公共祖先一定还没有被检查,
而这个最近公共祖先的包涵v的子树一定已经搜索过了,那么这个最近公共祖先一定是v 所在集合的祖先。
*/