Vijos P1460 拉力赛 倍增+LCA/时间戳
拉力赛
描述
车展结束后,游乐园决定举办一次盛大的山道拉力赛,平平和韵韵自然也要来参加大赛。
赛场上共有n个连通的计时点,n-1条赛道(构成了一棵树)。每个计时点的高度都不相同(父结点的高度必然大于子结点),相邻计时点间由赛道相连。由于马力不够,所以韵韵的遥控车只能从高处驶向低处。而且韵韵的车跑完每条赛道都需花费一定的时间。
举办方共拟举办m个赛段的比赛,每次从第u个计时点到第v个计时点,当然其中有不少比赛韵韵的遥控车是不能参加的(因为要上坡)。平平想知道他能参加多少个赛段的比赛,并且想知道他完成这些赛段的总用时。
赛道皆为单向。
格式
输入格式
第一行两个整数n,m。
接下来n-1行每行3个整数a、b、t。
表示韵韵的遥控车可以花t秒从第a个计时点到第b个计时点。
接下来m行每行2个整数u、v,意义如描述所示。
输出格式
第一行输出一个正整数,表示能参加的赛段数。
第二行输出一个正整数,表示总用时。
样例输入
6 2
1 2 1
2 4 1
2 5 1
5 6 1
1 3 1
2 6
4 5
样例输出
1
2
限制
各个测试点1s
提示
第一个计时点的高度是最高的;
u≠v;
对于50%的数据 n≤1000 m≤1000;
对于100%的数据 n≤10000 m≤100000;
答案小于2^64。
来源
f1zsy birdor
----------------------------------------------------------------------------------------------------------------------------------------
因为最近在学LCA,所以一看到这道题就果断码了 倍增+LCA。这道题本质就是判断u是否为v的祖先,AC代码:
1 #include<stdio.h> 2 #include<string.h> 3 #define maxn 233333 4 struct node{ 5 int to,next,w; 6 }; 7 node e[maxn]; 8 int n,m,cnt,pre[maxn],p[maxn][20],len[maxn],count,dis[maxn]; 9 long long sum; 10 void build(int,int,int); 11 void dfs(int); 12 void ycl(); 13 void lca(int,int); 14 void find(int,int); 15 int main(){ 16 scanf("%d %d",&n,&m); 17 cnt=0; 18 for(int i=1;i<n;i++){ 19 int u,v,c; 20 scanf("%d %d %d",&u,&v,&c); 21 build(u,v,c); 22 } 23 len[1]=1;dis[1]=0; 24 dfs(1); 25 ycl(); 26 count=0;sum=0; 27 for(int i=1;i<=m;i++){ 28 int x,y; 29 scanf("%d %d",&x,&y); 30 lca(x,y); 31 } 32 printf("%d\n%lld",count,sum); 33 return 0; 34 } 35 void build(int u,int v,int c){ 36 cnt++; 37 e[cnt].to=v;e[cnt].w=c;e[cnt].next=pre[u];pre[u]=cnt; 38 } 39 void dfs(int x){ 40 for(int i=pre[x];i;i=e[i].next){ 41 int to=e[i].to; 42 dis[to]=e[i].w+dis[x]; 43 len[to]=len[x]+1; 44 p[to][0]=x; 45 dfs(to); 46 } 47 } 48 void ycl(){ 49 for(int j=1;(1<<j)<=n;j++) 50 for(int i=1;i<=n;i++) 51 p[i][j]=p[p[i][j-1]][j-1]; 52 } 53 void lca(int a,int b){ 54 int x,y; 55 if(len[a]>len[b]) return; 56 x=a;y=b; 57 int fc=len[b]-len[a]; 58 for(int j=0;(1<<j)<=fc;j++) 59 if((1<<j)&fc) b=p[b][j]; 60 if(a==b){ 61 sum+=dis[y]-dis[x]; 62 count++; 63 } 64 }
但看了题解后,我发现了一个效率更高的方法,即运用时间戳,这样两遍DFS即可解决问题。所谓的时间戳,在本题中就是开两个数组,一个记录每个点先序遍历的先后顺序,另一个则记录每个点后序遍历的先后顺序;如果u是v的祖先,那么u的先序遍历顺序会在v前面,且u的后序遍历顺序会在v后面,利用这一点,即可以马上判断u是否为v的祖先。下面为AC代码:
1 #include<stdio.h> 2 #include<string.h> 3 #define maxn 233333 4 struct node{ 5 int to,next,w; 6 }; 7 node e[maxn]; 8 int n,m,pre[maxn],cnt,first[maxn],last[maxn],con,total,dis[maxn]; 9 long long sum; 10 int read(); 11 void dfsf(int); 12 void dfsl(int); 13 void build(int,int,int); 14 int main(){ 15 n=read();m=read();cnt=0; 16 for(int i=1;i<n;i++){ 17 int a=read(),b=read(),t=read(); 18 build(a,b,t); 19 } 20 con=0;dfsf(1);//先序遍历 21 con=0;dfsl(1);//后序遍历 22 sum=0;total=0; 23 for(int i=1;i<=m;i++){ 24 int u=read(),v=read(); 25 if(first[u]<=first[v]&&last[u]>=last[v]){//判断u是否为v的祖先 26 total++;sum+=dis[v]-dis[u]; 27 } 28 } 29 printf("%d\n%d",total,sum); 30 return 0; 31 } 32 int read(){ 33 int ans=0,f=1;char c=getchar(); 34 while('0'>c||c>'9'){if(c=='-')f=-1;c=getchar();} 35 while('0'<=c&&c<='9')ans=ans*10+c-48,c=getchar();return ans*f; 36 } 37 void build(int u,int v,int w){ 38 e[++cnt].to=v;e[cnt].next=pre[u];pre[u]=cnt;e[cnt].w=w; 39 } 40 void dfsf(int x){ 41 first[x]=++con; 42 for(int i=pre[x];i;i=e[i].next){ 43 int to=e[i].to; 44 dis[to]=dis[x]+e[i].w; 45 dfsf(to); 46 } 47 } 48 void dfsl(int x){ 49 for(int i=pre[x];i;i=e[i].next) dfsl(e[i].to); 50 last[x]=++con; 51 }