Codeforces Round #343 (Div. 2) E. Famil Door and Roads (树形dp,lca)

题意:给你一棵节点数为n的树,随机地在树上的任意两个点连一条边,给你m个询问,每次询问两个点,问连一条边后如果这两个点能在简单环中,简单环的期望是多少。
简单环即这两个点在一个环上,这个环是没有重边的。
思路:这里先设置几个变量dep[i]:i节点的深度,这里记dep[0]=0,dep[1]=1;sz[i]:i节点的子树的节点总数;f[i][j]:i节点的2^j倍父亲;sdown[i]:i节点子树中的所有点到i节点的距离和;sall[i]:所有点到i节点的距离和;t=lca(u,v).
     先考虑lca(u,v)!=u && lca(u,v)!=v的情况,想要使得u,v都在简单环中,那么连边的两个端点一定是一个在u的子树中,另一个在v的子树中,且连边的方案数为sz[u]*sz[v],那么我们得到的期望值是sdown[u]/sz[u]+sdown[v]/sz[v]+1+dep[u]+dep[v]-2*dep[t].这里dep[u]+dep[v]-2*dep[t]+1是每一个形成的简单环都有的长度,所以可以先加上去.
     然后考虑lca(u,v)==u || lca(u,v)==v的情况,不妨假设lca(u,v)=v,那么连边的两个端点一端一定在u的子树中,另一端在v的上面,即树上的所有点除去不包括u这个节点的子树,我们得到的期望值是sdown[u]/sz[u]+(sall[v]-sdown[v1]-sz[v1])/(n-sz[v1]) (v1是u,v路径上v的子节点).

第一次dfs先求出sdown[i],然后第二次dfs就能求出sall[i]了.

[cpp]  view plain  copy
  1. #include<iostream>  
  2. #include<stdio.h>  
  3. #include<stdlib.h>  
  4. #include<string.h>  
  5. #include<math.h>  
  6. #include<vector>  
  7. #include<map>  
  8. #include<set>  
  9. #include<queue>  
  10. #include<stack>  
  11. #include<string>  
  12. #include<algorithm>  
  13. using namespace std;  
  14. typedef long long ll;  
  15. typedef long double ldb;  
  16. #define inf 99999999  
  17. #define pi acos(-1.0)  
  18. #define maxn 100050  
  19.   
  20. int sz[maxn],dep[maxn],f[maxn][23];  
  21. ll sdown[maxn],sall[maxn];  
  22. int n;  
  23. struct edge{  
  24.     int to,next;  
  25. }e[2*maxn];  
  26. int first[maxn];  
  27. void dfs1(int u,int father,int deep)  
  28. {  
  29.     int i,j,v;  
  30.     dep[u]=dep[father]+1;  
  31.     sz[u]=1;sdown[u]=0;  
  32.     for(i=first[u];i!=-1;i=e[i].next){  
  33.         v=e[i].to;  
  34.         if(v==father)continue;  
  35.         f[v][0]=u;  
  36.         dfs1(v,u,dep[u]);  
  37.         sz[u]+=sz[v];  
  38.         sdown[u]+=sdown[v]+sz[v];  
  39.     }  
  40. }  
  41.   
  42. void dfs2(int u,int father)  
  43. {  
  44.     int i,j,v;  
  45.     for(i=first[u];i!=-1;i=e[i].next){  
  46.         v=e[i].to;  
  47.         if(v==father)continue;  
  48.         sall[v]=sall[u]+n-2*sz[v]; //这里是主要的公式,可以这样理解:所有点到父亲节点u的距离和sall[u]已经算出来了,那么算v这个节点的时候,不在v子树范围内的点到v的距离都多了1,所以加上n-sz[v],v的子树的点到v的距离都减少了1,所以要减去sz[v].  
  49.         dfs2(v,u);  
  50.     }  
  51. }  
  52. void init()  
  53. {  
  54.     dep[0]=0;  
  55.     dfs1(1,0,0);  
  56.     sall[1]=sdown[1];  
  57.     dfs2(1,0);  
  58. }  
  59. int lca(int x,int y){  
  60.     int i;  
  61.     if(dep[x]<dep[y]){  
  62.         swap(x,y);  
  63.     }  
  64.     for(i=20;i>=0;i--){  
  65.         if(dep[f[x][i] ]>=dep[y]){  
  66.             x=f[x][i];  
  67.         }  
  68.     }  
  69.     if(x==y)return x;  
  70.     for(i=20;i>=0;i--){  
  71.         if(f[x][i]!=f[y][i]){  
  72.             x=f[x][i];y=f[y][i];  
  73.         }  
  74.     }  
  75.     return f[x][0];  
  76. }  
  77. int up(int u,int deep)  
  78. {  
  79.     int i,j;  
  80.     for(i=20;i>=0;i--){  
  81.         if((1<<i)<=deep){  
  82.             u=f[u][i];  
  83.             deep-=(1<<i);  
  84.         }  
  85.     }  
  86.     return u;  
  87.   
  88. }  
  89. int main()  
  90. {  
  91.     int m,i,j,tot,c,d,v,u,k;  
  92.     double sum;  
  93.     while(scanf("%d%d",&n,&m)!=EOF)  
  94.     {  
  95.         tot=0;  
  96.         memset(first,-1,sizeof(first));  
  97.         for(i=1;i<=n-1;i++){  
  98.             scanf("%d%d",&c,&d);  
  99.             tot++;  
  100.             e[tot].next=first[c];e[tot].to=d;  
  101.             first[c]=tot;  
  102.   
  103.             tot++;  
  104.             e[tot].next=first[d];e[tot].to=c;  
  105.             first[d]=tot;  
  106.         }  
  107.         init();  
  108.         for(k=1;k<=20;k++){  
  109.             for(i=1;i<=n;i++){  
  110.                 f[i][k]=f[f[i][k-1]][k-1];  
  111.             }  
  112.         }  
  113.         for(i=1;i<=m;i++){  
  114.             scanf("%d%d",&u,&v);  
  115.             int t=lca(u,v);  
  116.             sum=(double)(dep[u]+dep[v]-2*dep[t])+1;  
  117.             if(t==u || t==v){  
  118.                 if(t==u)swap(u,v);  
  119.                 int v1=up(u,dep[u]-dep[v]-1);  
  120.                 ll num1=sall[v]-sdown[v1]-sz[v1];  
  121.                 sum+=(double)sdown[u]/(double)sz[u]+(double)(num1)/(double)(n-sz[v1]);  
  122.                 printf("%.10f\n",sum);  
  123.             }  
  124.             else{  
  125.                 sum+=(double)sdown[u]/(double)sz[u]+(double)sdown[v]/(double)sz[v];  
  126.                 printf("%.10f\n",sum);  
  127.             }  
  128.         }  
  129.     }  
  130.     return 0;  
  131. }  
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值