先上题目:
How far away ?
Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 4936 Accepted Submission(s): 1866
Problem Description
There are n houses in the village and some bidirectional roads connecting them. Every day peole always like to ask like this "How far is it if I want to go from house A to house B"? Usually it hard to answer. But luckily int this village the answer is always unique, since the roads are built in the way that there is a unique simple path("simple" means you can't visit a place twice) between every two houses. Yout task is to answer all these curious people.
Input
First line is a single integer T(T<=10), indicating the number of test cases.
For each test case,in the first line there are two numbers n(2<=n<=40000) and m (1<=m<=200),the number of houses and the number of queries. The following n-1 lines each consisting three numbers i,j,k, separated bu a single space, meaning that there is a road connecting house i and house j,with length k(0<k<=40000).The houses are labeled from 1 to n.
Next m lines each has distinct integers i and j, you areato answer the distance between house i and house j.
For each test case,in the first line there are two numbers n(2<=n<=40000) and m (1<=m<=200),the number of houses and the number of queries. The following n-1 lines each consisting three numbers i,j,k, separated bu a single space, meaning that there is a road connecting house i and house j,with length k(0<k<=40000).The houses are labeled from 1 to n.
Next m lines each has distinct integers i and j, you areato answer the distance between house i and house j.
Output
For each test case,output m lines. Each line represents the answer of the query. Output a bland line after each test case.
Sample Input
2
3 2
1 2 10
3 1 15
1 2
2 3
2 2
1 2 100
1 2
2 1
Sample Output
10
25
100
100
题意:给出一棵树,然后给出树上的两个点,求两个点的最短距离。
如果是求距离总和的话可以用树形DP,这里使用的是求LCA,求LCA的方法有很多,我这里用的是LCA转RMQ,顺便把理解的东西记录下来。
首先我们先序遍历一次这一棵树,同时记录下每一个点的深度(重要),同时按照访问点的顺序构造一个序列,就是每一次访问一个点,就在序列末尾添加这一个点,需要注意的是在子树回溯会根节点的时候根节点也要再次入列一次,同时我们还需要记录下每一个点第一次访问的时候在这个序列的哪一个位置。为什么要这样做?我们可以发现,在顶层的点的深度一定是比子树的节点的深度小的,而对于序列里面的数,我们可以发现,找某两个点的最近公共祖先,那么这个最近公共祖先一定在序列中这两个点所包围的序列中间出现过,因为这是先序遍历得到的序列。那个我们可以跑一次RMQ就可以得到最近公共祖先的深度或者就可以直接得到最近公共祖先。
对于这一题,我们得到最近公共祖先的深度以后从当前点回溯上去,同时加上走过的路径长度,最终就可以得到结果了。
上代码:
1 #include <cstdio> 2 #include <cstring> 3 #include <algorithm> 4 #include <cmath> 5 #define MAX 40002 6 using namespace std; 7 8 typedef struct{ 9 int from,to,next,l; 10 }Edge; 11 12 Edge e[MAX<<1]; 13 int p[MAX],fa[MAX],tot; //fa:保存当前点的父亲是谁 14 int r[MAX<<1],all; //r保存先序遍历的序列 15 int d[MAX]; //d保存每个点的深度 16 int dp[MAX<<1][30],len[MAX]; //len保存当前点回到父亲节点的路劲长度 17 int vis[MAX]; //保存第一次出现在r[]里面的位置 18 19 inline void add(int u,int v,int l){ 20 e[tot].from=u; e[tot].to=v; e[tot].l=l; e[tot].next=p[u]; p[u]=tot++; 21 } 22 23 void dfs(int u,int par,int dep,int l){ 24 d[u]=dep; 25 fa[u]=par; 26 vis[u]=all; 27 r[all++]=u; 28 len[u]=l; 29 for(int i=p[u];i!=-1;i=e[i].next){ 30 if(vis[e[i].to]==-1){ 31 dfs(e[i].to,u,dep+1,e[i].l); 32 r[all++]=u; 33 } 34 } 35 } 36 37 void rmq(){ 38 for(int i=0;i<all;i++) dp[i][0]=d[r[i]]; 39 for(int j=1;(1<<j)<=all;j++){ 40 for(int i=0;i+(1<<j)-1<all;i++){ 41 dp[i][j]=min(dp[i][j-1],dp[i+(1<<(j-1))][j-1]); 42 } 43 } 44 } 45 46 int query(int x,int y){ 47 x = vis[x]; 48 y = vis[y]; 49 if(x>y) swap(x,y); 50 int minn,k; 51 k = log(y-x+1.0) / log(2.0); 52 minn=min(dp[x][k],dp[y-(1<<k)+1][k]); 53 return minn; 54 } 55 56 int dist(int x,int pxy){ 57 int sum=0; 58 while(d[x]!=pxy){ 59 sum+=len[x]; 60 x = fa[x]; 61 } 62 return sum; 63 } 64 65 int main() 66 { 67 int t,n,m,x,y,l,pxy,sum; 68 //freopen("data.txt","r",stdin); 69 scanf("%d",&t); 70 while(t--){ 71 scanf("%d %d",&n,&m); 72 tot=0; 73 memset(p,-1,sizeof(p)); 74 for(int i=1;i<n;i++){ 75 scanf("%d %d %d",&x,&y,&l); 76 add(x,y,l); 77 add(y,x,l); 78 } 79 all=0; 80 memset(vis,-1,sizeof(vis)); 81 dfs(1,1,1,0); 82 rmq(); 83 while(m--){ 84 scanf("%d %d",&x,&y); 85 pxy = query(x,y); 86 sum = dist(x,pxy) + dist(y,pxy); 87 printf("%d ",sum); 88 } 89 } 90 return 0; 91 }