codevs 1036 商务旅行 (倍增LCA)

/*
在我还不知道LCA之前 暴力跑的SPFA 
70分 三个点TLE 
*/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
struct node
{
    int u;
    int t;
    int pre;
};
node a[30010*2+100];
queue<int>q;
int n,qq,x[30010],num,head[30010],f[30010],dis[30010],sum;
void add(int from,int to)
{
    num++;
    a[num].pre=head[from];
    a[num].u=to;
    a[num].t=1;
    head[from]=num;
}
void SPFA()
{
    int i,j;
    for(j=1;j<=qq-1;j++)
      {
          memset(dis,127/3,sizeof(dis));
        memset(f,0,sizeof(f));
          int s=x[j];
          int v=x[j+1];
          q.push(s);
          f[s]=1;
          dis[s]=0;
          do
          {
              int k=q.front();
              q.pop();
              f[k]=0;
              for(i=head[k];i;i=a[i].pre)
                {
                    if(dis[a[i].u]>dis[k]+a[i].t)
                      {
                          dis[a[i].u]=dis[k]+a[i].t;
                          if(f[a[i].u]==0)
                            {
                                q.push(a[i].u);
                                f[a[i].u]=1;
                      }
                  }
              }
          }while(!q.empty());
        sum=sum+dis[v];
      } 
}
int main()
{
    int i,o,u;
    cin>>n;
    for(i=1;i<=n-1;i++)
      {
          cin>>o>>u;
          add(o,u);
          add(u,o);
      }
    cin>>qq;
    for(i=1;i<=qq;i++)
      cin>>x[i];
    SPFA();
    cout<<sum;
}
/*
倍增LCA 
将旅行路线两两拆开 求相邻两点的距离 
即求他们到lca的距离和 
这里用倍增法实现求距离 fa[i][j]表示i节点往上2^j层的节点是什么
因为1 2 4 8 16...做和可以表示所有的整数 所以保证覆盖整张图
先Dfs建图 顺面几下每个节点的所在层 以及fa[i][0] 的值
然后DP预处理fa数组
然后就是求到lca的距离了
这里我们求出只需要求出lca是谁 那么两点之间的最小距离就是
deep[p1]+deep[p2]-2*deep[anc] deep是深度
剩下的就仅仅是找lca是谁了
对于一组a ,b  如果他们在同一层的话 我们只需要同时向上移动他俩 知道a==b
 
*/
#include<iostream>
#include<cstdio>
#include<cstring>
#define maxn 30010
#define S 16
int n,m,num,head[maxn],fa[maxn][S+5],deep[maxn],p1,p2,ans;
struct node
{
    int u,v,pre;
}e[maxn*2];
void Add(int from,int to)
{
    num++;
    e[num].u=from;
    e[num].v=to;
    e[num].pre=head[from];
    head[from]=num;
}
void swap(int &a,int &b)
{
    int t=a;a=b;b=t;
}
void init()
{
    scanf("%d",&n);
    int x,y;
    for(int i=1;i<=n-1;i++)
      {
          scanf("%d%d",&x,&y);
          Add(x,y);Add(y,x);
      }
}
void get_fa()
{
    for(int j=1;j<=S;j++)
      for(int i=1;i<=n;i++)
        fa[i][j]=fa[fa[i][j-1]][j-1];//数学方法展开就是fa[i][j] 
}
void Dfs(int now,int from,int c)//得带每个点的所在层 
{
    fa[now][0]=from;
    deep[now]=c;
    for(int i=head[now];i;i=e[i].pre)
      if(e[i].v!=from)
        Dfs(e[i].v,now,c+1);
}
int get_same(int a,int t)//计算a往上t层是啥 
{
    for(int i=1;i<=t;i++)
      a=fa[a][0];
    return a;
}
int LCA(int a,int b)
{
    if(deep[a]<deep[b])swap(a,b);//保证a深度大 
    a=get_same(a,deep[a]-deep[b]);//得到a向上deep[a]-deep[b]层是谁 即a的与b同层的祖先是谁 
    if(a==b)return a;//如果汇聚到一个点 就不用往上了 
    for(int i=S;i>=0;i--)//这里有套路 从最大值开始循环 一开始 fa[a][i]  fa[b][i]一定是相等的
                         //因为此时位于他们lca之上 然后往下 这里可以覆盖每一层 
      if(fa[a][i]!=fa[b][i])
        {
          a=fa[a][i];
          b=fa[b][i];
        }
    return fa[a][0];
}
int main()
{
    init();
    Dfs(1,1,0);
    get_fa();
    scanf("%d%d",&m,&p1);
    for(int i=1;i<=m-1;i++)
      {
          scanf("%d",&p2);
          int anc=LCA(p1,p2);
          ans+=deep[p1]+deep[p2]-2*deep[anc];
          p1=p2;
      }
    printf("%d\n",ans);
    return 0;
}

 

posted @ 2016-05-30 14:53  一入OI深似海  阅读(250)  评论(0编辑  收藏  举报