1. 程式人生 > >The North American Invitational Programming Contest 2016 I.Tourists(LCA求樹上任意兩點距離+埃式篩法)

The North American Invitational Programming Contest 2016 I.Tourists(LCA求樹上任意兩點距離+埃式篩法)

思路來源

題意

給定一棵樹,求

\sum dis(i,j),1<=i<j<=n,i|j

dis(i,j)定義為樹上兩點距離+1,n≤2e5

題解

dfs預處理,求根節點到每一點的距離,即點i的深度depth[i]。

這裡預設1號點是根節點。

然後線上二分搜尋+倍增求LCA,求i和j的最近公共祖先z。

即使樹退化成連結串列,倍增也能logn解決。

i和j的距離就是depth[i]+depth[j]-2*depth[z],

即i到z的距離+j到z的距離。

然後埃氏篩法一波就A了。

程式碼

  #include<stdio.h>
  #include<string.h>
  #include<algorithm>
  using namespace std;
  typedef long long ll;
  ll cnt,head[200010];
  struct edge{ll to,nex;}e[400010];
  ll parent[200010][30],depth[200010];
  ll ans;
  void init()
  {
	memset(head,-1,sizeof(head));
	cnt=0;
  }
  void add(ll u,ll v)
  {
	e[cnt].to=v;
	e[cnt].nex=head[u];
	head[u]=cnt++;
  } 
 void dfs(ll u,ll pre,ll deep)
 {
   parent[u][0]=pre;
   depth[u]=deep;
   for (int i=head[u];~i;i=e[i].nex)
   {
   	 ll v=e[i].to;
     if(v==pre) continue;
     dfs(v,u,deep+1);
   }
 }
 void double_build(ll n)
 {
   int i,j; 
   for (i=0;i+1<=25;i++)//路徑倍增
     for (j=1;j<=n;j++)
       if (parent[j][i]<0) parent[j][i+1]=-1;
       else parent[j][i+1]=parent[parent[j][i]][i];
 }
 ll double_query(ll u,ll v)
 {
   int i;
   if (depth[u]>depth[v]) swap(u,v);
   for (i=0;i<=25;i++)
     if ((depth[v]-depth[u])>>i&1)
       v=parent[v][i];
   if (u==v) return u;
   for (i=25;i>=0;i--) //二進位制拆分思想達到倍增
     if (parent[u][i]!=parent[v][i])
     {
       u=parent[u][i];
       v=parent[v][i];
     }
   return parent[u][0];
 }
 int main()
 {
     ll x,y,n;
     scanf("%lld",&n);
     init();
     for(int i=1;i<n;i++)
     {
       scanf("%lld%lld",&x,&y);
       add(x,y);
	   add(y,x);
     }
     dfs(1,-1,1);
     double_build(n);
     for(int i=1;i<=n;++i)
     {
     	for(int j=2*i;j<=n;j+=i)
     	{ 
         ll z=double_query(i,j);
         ans+=1+depth[i]+depth[j]-2*(depth[z]);//距離計算
         //printf("%d->%d=%lld\n",i,j,1+depth[i]+depth[j]-2*(depth[z]));
        } 
     }
     printf("%lld\n",ans);
     return 0;
 }