重逢 图论(未完待续)

图论:你好,sugar,我们终于又见面了!

sugar:什么,我们见过面吗??(天那!我竟然一点都不记得!!)

图论:什么,不记得我了。~~~~(>_<)~~~~宝宝表示很伤心。

sugar:sorry,那个,要不我们再从新认识一下吧、、、、、

图论:好吧。。。。。(忧伤的低下了头,呜呜,人家可是很重要的,竟然把人家忘了,哼!)

        一、tarjan求强连通分量、缩点

http://www.cnblogs.com/z360/p/7010712.html

http://www.cnblogs.com/z360/p/7029416.html

Tarjan算法是一个通过对图进行深度优先搜索并通过同时维护一个栈以及两个相关时间戳的算法。 定义dfn(u)表示节点u搜索的次序编号(时间戳,第几个搜索的节点),low(u)表示节点u或u的子树当中可以找到的最早的栈中的节点的时间戳。

由定义,我们可以得到: Low(u)=dfn(u), low(u)=min(low(u),low(v))当(u,v)为搜索树当中的树枝边。 low(u)=min(low(u),dfn(v))当(u,v)为搜索树当中的后向边(也就是v仍在栈内的情况) 每当找到一个节点在遍历完后返回时low(u)=dfn(u),那么可以弹出栈中u以上的所有节点,此时这里的所有节点形成一个强连通分量

当我们在求出强连通分量之后,我们可以把同一个强连通分量里的点当成新图里的一个新点,然后如果原图中两个不在同一个强连通分量的点有连边的话,那么他们所对应的新点也有连边。新图是一个有向无环图

模板:

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#define N
struct Edge
{
     int from,to,next;
}edge[N];
int add(int x,int y)
{
     tot++;
     edge[tot].to=y;
     edge[tot].next=head[x];
     head[x]=tot;
}
int read() 
{
     int x=0,f=1; char ch=getchar();
     while(ch<'0'||ch>'9'){if(ch=='-')f=-1; ch=getchar();}
     while(ch>='0'&&ch<='9'){x=x*10+ch-'0'; ch=getchar();}
     return x*f;
}
int tarjan(int now)//tarjan求强连通分量,sum相同的在一个强连通里 
{
     dfn[now]=low[now]=++tim;
     stack[++top]=now,vis[now]=true;
     for(int i=head[now];i;i=edge[iw].next)
     {
         int to=edge[i].to;
         if(vis[to]) low[now]=min(dfn[to],low[now]);
         else if(!dfn[to]) tarjan(to),low[now]=min(low[to],low[now]);
     }
     if(low[now]==dfn[now])
     {
         sum++;belong[now]=sum;
         for(;stack[top]!=now;top--)
         {
              belong[stack[top]]=sum;
              vis[stack[top]]=false;
         }
            vis[now]=false;top--;
     }
}
int shink_point()//缩点 
{
     for(int i=1;i<=n;i++)
      for(int j=head[i];j;j=edge[j].next)
       {
           int x=edge[j].to;
           if(belong[i]!=belong[x]) add(belong[i],belong[x]);
       }
}
int main()
{
     n=read(),m=read();
     for(int i=1;i<=m;i++)
       x=read(),y=read(),add(x,y);
     for(int i=1;i<=n;i++)
       if(!dfn[i]) tarjan(i);
     shrink_point();
     return 0;
}
强连通分量 缩点

常见题型:1.给定一张有向图,求加多少条边之后整个图是一张强连通分量。   如果一个图是一个强连通分量的话,那么他缩完点以后一定没有入读与出度为零的点。这样的话,我们知道,我们加上边以后只要让他满足出度跟入度都不为0的话,那么他就是一个强连通了。所以,我们·加边的数量就等于入度与出度为零的点的最大值。

2.问从一个点发出的信号最少经过多长时间他从别人那收到。把每个人说话的对象作为这个人连出去的边。那么,我们得到一个N个点N条边的图。并且每个点只有一个出边。因此,图必然是许多个类似的连通块,而每个连通块都是一棵树加一条边,这条边与原本的树边形成一个环。实际上我们只需要找出最小环就可以了。

3.我们知道一些关系,问最少让多少人知道信息可以让所有都知道(每个人可以将信息穿给他认识的人&&那个人愿意相信他)  我们tarjan缩点,然后求入读为0的点的个数就好了、、、、

         二、tarjan求割边割点

http://www.cnblogs.com/z360/p/7056604.html

割边:对于无向连通图来说,如果删除一条边之后使得这个图不连通,那么该边为割边

割点:对于无向连通图来说,如果删除一个点以及与它相连的边之后,那么该点为割点

1.割点的求法

对于一个点u,

1.u为dfs搜索树的根,如果搜索树中它有大于一个的子节点,那么为割点;

2.u不为dfs搜索树的根,如果u存在子节点v使low[v]>=dfn[u],那么u为割点

2.割边的求法

对于一条边(u,v),

1.如果(u,v)是后向边,一定不是割边;

2.如果(u,v)是树边,那么如果low[v]>dfn[u],那么是割边;

代码:

 

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#define N 200010
using namespace std;
int n,m,x,y,tim,tot,ans;
int low[N],dfn[N],vis[N],head[N],cut_edge[N],cut_point[N];
int read()
{
    int x=0,f=1; char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1; ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0'; ch=getchar();}
    return x*f;
}
struct Edge
{
    int from,to,next;
}edge[N];
int add(int x,int y)
{
    edge[tot].to=y;
    edge[tot].next=head[x];
    head[x]=tot;
    tot++;
}
void tarjan(int now,int pre)
{
    int sum=0;bool boo=false;vis[now]=true;
    dfn[now]=low[now]=++tim;
    for(int i=head[now];i;i=edge[i].next)
    {
        int t=edge[i].to;
        if((i^1)==pre) continue;
        if(!dfn[t])
        {
             sum++;tarjan(t,i);
            low[now]=min(low[now],low[t]);
             if(low[t]>dfn[now])  cut_edge[i/2]=1;
             if(low[t]>=dfn[now]) boo=1;
        }
        else low[now]=min(low[now],dfn[t]);
    } 
    if(!pre) {if(sum>1) ans++,cut_point[now]=true;}
    else if(boo) {ans++;cut_point[now]=true;}
}
int main()
{
    n=read(),m=read();
    for(int i=1;i<=m;i++)
      x=read(),y=read(),add(x,y),add(y,x);
    for(int i=1;i<=n;i++)
      if(!dfn[i]) tarjan(i,0);
    printf("%d\n",ans);
    for(int i=1;i<=n;i++)
      if(cut_point[i]) printf("%d ",i);
    return 0;
}
tarjan割边割点

            三、最小生成树

http://www.cnblogs.com/z360/p/6853637.html

Kruskal算法主要分为两步:
给所有边按照边权从小到大的顺序排序;
从小到大依次考虑每条边(u,v)(最开始没有任何的边):
如果u与v已经连通了,那么加入(u,v)后会出现环,不添加;
如果u与v没有连通,那么加入(u,v)使其连通。
然后,对于判断是否联通,我们可以通过并查集来维护。
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#define N 100010
using namespace std;
int n,m,x,y,z,fa[N];
int read()
{
    int x=0,f=1; char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1; ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0'; ch=getchar();}
    return x*f;
}
struct Edge
{
    int x,y,z;
}edge[N];
int cmp(Edge a,Edge b)
{
    return a.z<b.z;
}
int found(int x)
{
    if(x==fa[x]) return x;
    fa[x]=found(fa[x]);
    return fa[x];
}
int main()
{
    n=read(),m=read();
    for(int i=1;i<=m;i++)
    {
        x=read(),y=read(),z=read();
        edge[i].x=x;
        edge[i].y=y;
        edge[i].z=z;
    } 
    sort(edge+1,edge+1+m,cmp);
    for(int i=1;i<=n;i++) fa[i]=i;
    int ans=0;
    for(int i=1;i<=m;i++)
    {
        x=edge[i].x;y=edge[i].x;
        int fx=found(x),fy=found(y);
        if(fx==fy) continue;
        fa[fx]=fy;
        ans+=edge[i].z;
    }
    printf("%d\n",ans);
    return 0;
}
最下生成树

 我们求把所有的点都连起来所需的最小代价。

次小生成树

http://www.cnblogs.com/z360/p/6875488.html

求次小生成树的两种方法

1:首先求出最小生成树T,然后枚举最小生成树上的边,计算除了枚举的当前最小
生成树的边以外的所有边形成的最小生成树Ti,然后求最小的Ti就是次小生成树。
2:首先计算出最小生成树T,然后对最小生成树上任意不相邻的两个点 (i,j)
添加最小生成树以外的存在的边形成环,然后寻找i与j之间最小生成树上最长的边删去,
计算map[i][j](最小生成树以外存在的边) 与 maxd[i][j](最小生成树上最长的边)
差值,求出最小的来,w(T)再加上最小的差值就是次小生成树了。

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#define N 300010
using namespace std;
int n,m,x,y,z,k,sum,tot,num,answer=N,fa[N],ans[N];
int read()
{
    int x=0,f=1; char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1; ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0'; ch=getchar();}
    return x*f;
}
struct Edge
{
    int x,y,z;
}edge[N];
int cmp(Edge a,Edge b)
{
    return a.z<b.z;
}
int find(int x)
{
    if(x==fa[x]) return x;
    fa[x]=find(fa[x]);
    return fa[x];
}
int main()
{
    freopen("mst2.in","r",stdin);
    freopen("mst2.out","w",stdout);
    n=read(),m=read();
    for(int i=1;i<=m;i++)
    {
        x=read(),y=read(),z=read();
        edge[i].x=x;
        edge[i].y=y;
        edge[i].z=z;
    }
    for(int i=1;i<=n;i++) fa[i]=i;
    sort(edge+1,edge+1+m,cmp);
    for(int i=1;i<=m;i++)
    {
        int fx=find(edge[i].x),fy=find(edge[i].y);
        if(fx==fy) continue;
        tot++;fa[fx]=fy;
        ans[tot]=i;sum+=edge[i].z;
        if(tot==n-1) break;
    }
    for(int i=1;i<=tot;i++)
    {
        k=0,num=0;
        for(int j=1;j<=n;j++) fa[j]=j;
        sort(edge+1,edge+1+m,cmp);
        for(int j=1;j<=m;j++)
        {
            if(j==ans[i]) continue;
            int fx=find(edge[j].x),fy=find(edge[j].y);
            if(fx!=fy)
            {
                fa[fx]=fy;
                num++;
                k+=edge[j].z;
            }
            if(num==n-1) break;
        }
        if(num==n-1&&k!=sum) answer=min(k,answer);
    }
    printf("%d",answer);
}
次小生成树

                     四、最短路

floyd:http://www.cnblogs.com/z360/p/6790139.html

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#define N 200
#define maxn 9999999
using namespace std;
bool flag;
int n,m,x,y,z,s,ans1,ans2,dis[N][N];
int read()
{
    int x=0,f=1; char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1; ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0'; ch=getchar();}
    return x*f;
}
void begin()
{
    for(int i=1;i<=n;i++)
     for(int j=1;j<=n;j++)
      dis[i][j]=(i!=j)*maxn;
}
void floyd()
{
    for(int k=1;k<=n;k++)
     for(int i=1;i<=n;i++)
      for(int j=1;j<=n;j++)
       dis[i][j]=min(dis[i][k]+dis[k][j],dis[i][j]);
}
int main()
{
    while(1)
    {
        n=read();begin();
        if(n==0) break;
        for(int i=1;i<=n;i++)
        {
            m=read();
            for(int j=1;j<=m;j++)
              y=read(),z=read(),dis[i][y]=z;
        }
        floyd();flag=false;
        ans1=0,ans2=maxn;
        for(int i=1;i<=n;i++)
        {
            s=0;
            for(int j=1;j<=n;j++)
            {
                if(i==j) continue;
                s=max(s,dis[i][j]);
             } 
            if(s<ans2) ans1=i,ans2=s;
            if(s!=maxn) flag=true;
        }
        if(!flag) printf("disjoint\n");
        else printf("%d %d\n",ans1,ans2);
    }
    return 0;
}
floyd

 spfa:http://www.cnblogs.com/z360/p/6881746.html

建立一个队列,初始时队列里只有起始点,再建立一个表格记录起始点到所有点的最短路径(该表格的初始值要赋为极大值,该点到他本身的路径赋为0)。然后执行松弛操作,用队列里有的点作为起始点去刷新到所有点的最短路,如果刷新成功且被刷新点不在队列中则把该点加入到队列最后。重复执行直到队列为空。

说白了就是:先拿一个点(起点)入队,然后那这个点与他相连的边进行更新最小值,若更新成功,把相连的点加入队列中,改点弹出,重复上诉操作,直到队列变成空。这是我们所要求的对短路都放在了dis数组里。

#include<queue>
#include<cstdio>
#define INF 2147483647LL
using namespace std;
struct node {
    int to,dis,next;
}edge[500005];
int n,m,num,head[10001],dis[10001];//n 点的个数   m 连边的条数   s 起点   dis_1 储存最小边 
inline void edge_add(int from,int to,int dis)
{
    num++;
    edge[num].to=to;
    edge[num].dis=dis;
    edge[num].next=head[from];
    head[from]=num;
}
void SPFA(int start)
{
    queue<int>que;
    bool if_in_spfa[10001];
    for(int i=1;i<=n;i++) dis[i]=INF,if_in_spfa[i]=false;//初始化 
    dis[start]=0,if_in_spfa[start]=true;//加入第一个点(起点) 
    que.push(start);//将起点入队 
    while(!que.empty())//如果队列不为空,就接着执行操作,直到队列为空 
    {
        int cur_1=que.front();//取出队列的头元素 
        que.pop();//将队列头元素弹出 
        for(int i=head[cur_1];i;i=edge[i].next)//枚举与该点连接的边 
        {
            if(dis[cur_1]+edge[i].dis<dis[edge[i].to])//如果能更新最小值 
            {
                dis[edge[i].to]=edge[i].dis+dis[cur_1];//更新最小值 
                if(!if_in_spfa[edge[i].to])//将所能更新的没入队的元素入队 
                {
                    if_in_spfa[edge[i].to]=true;//标记为已入队 
                    que.push(edge[i].to);//推入队中 
                }
            }
        }
        if_in_spfa[cur_1]=false;//将该点标记为出队列 
    }
}

int main()
{
    int s;
    scanf("%d%d%d",&n,&m,&s);
    int from,to,dis;
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d%d",&from,&to,&dis);
        edge_add(from,to,dis);//用邻接链表储存 
    }
    SPFA(s);//从起点开始spfa 
    for(int i=1;i<=n;i++) printf("%d ",dis[i]);
    return 0;
}
spfa

 判断有无负环:如果某个点进入队列的次数超过N次则存在负环(SPFA无法处理带负环的图)

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#define N 200010
using namespace std;
bool vis[N],vist;
int n,m,t,x,y,z,tot,head[N],dis[N];
int read()
{
    int x=0,f=1; char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1; ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0'; ch=getchar();}
    return x*f;
}
struct Edge
{
    int to,dis,from,next;
}edge[N<<1];
int add(int x,int y,int z)
{
    tot++;
    edge[tot].to=y;
    edge[tot].dis=z;
    edge[tot].next=head[x];
    head[x]=tot;
}
void begin()
{
    tot=vist=0;
    memset(dis,0,sizeof(dis));
    memset(head,0,sizeof(head));
    memset(vis,false,sizeof(vis));
}
int spfa(int s)
{
    vis[s]=true;
    for(int i=head[s];i;i=edge[i].next)
    {
        int t=edge[i].to;
        if(dis[s]+edge[i].dis<dis[t])
        {
            dis[t]=dis[s]+edge[i].dis;
            if(vis[t]||vist)
            {
                vist=true;
                break;
            }
            spfa(t);
        }
    }
    vis[s]=false;
}
int main()
{
    t=read();
    while(t--)
    {
        n=read(),m=read();begin();
        for(int i=1;i<=m;i++)
        {
            x=read(),y=read(),z=read();
            add(x,y,z);
            if(z>=0) add(y,x,z);
        }
        for(int i=1;i<=n;i++)
        {
            spfa(i);
            if(vist) break;
        }
        if(vist) printf("YE5\n");
        else printf("N0\n");
    }
    return 0;
}
spfa判负环

 

int spfa(int x)
{
    vis[x]=true;
    for(int i=head[x];i;i=edge[i].next)
    {
        int t=edge[i].to;
        if(dis[t]>dis[x]+w[i])
        {
            dis[t]=dis[x]+w[i];
            if(vis[t]||spfa(t))
            {
                vis[x]=false;
                return true;
            }  
        }
    }
    vis[x]=false;
    return false;
}
dfs的spfa判负环

 

次短路:

先跑两遍spfa,一遍正向,一遍逆向。然后枚举每一条必须加入的边,计算出加入这条边后的路径长度为从前面跑到该边的前一个节点的最短路+从该边的后一个节点到最后的最短路+改边的长度;判断这条边是不是比最短路大,且为比最短路大的中的最小的。

#include<queue>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#define N 510000
#define maxn 99999999
using namespace std;
bool vis[N];
int n,m,x,y,z,sum,ans,tot,d1[N],d2[N],head[N];
queue<int>q;
struct Edge
{
    int to,dis,from,next;
}edge[N<<1];
int add(int x,int y,int z)
{
    tot++;
    edge[tot].to=y;
    edge[tot].dis=z;
    edge[tot].next=head[x];
    head[x]=tot;
}
int read()
{
    int x=0,f=1; char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1; ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0'; ch=getchar();}
    return x*f;
}
int spfa(int s,int *dis)
{
    for(int i=1;i<=n;i++) dis[i]=maxn,vis[i]=false;
    vis[s]=true,dis[s]=0; q.push(s);
    while(!q.empty())
    {
        int x=q.front();q.pop();vis[x]=false;
        for(int i=head[x];i;i=edge[i].next)
        {
            int t=edge[i].to;
            if(dis[t]>dis[x]+edge[i].dis)
            {
                dis[t]=dis[x]+edge[i].dis;
                if(!vis[t]) vis[t]=true,q.push(t);
            }
        }
    }
}
int main()
{
    n=read(),m=read();
    for(int i=1;i<=m;i++)
    {
        x=read(),y=read(),z=read();
        add(x,y,z),add(y,x,z);
     } 
    spfa(1,d1),spfa(n,d2);
    ans=maxn;
    for(int i=1;i<=n;i++)
     for(int j=head[i];j;j=edge[j].next)
     {
         int t=edge[j].to;
         sum=d1[i]+d2[t]+edge[j].dis;
         if(sum>d1[n]&&sum<ans) ans=sum;
     }
    printf("%d",ans);
    return 0;
}
次短路

                     五、拓扑排序   

 http://www.cnblogs.com/z360/p/6891705.html

拓扑排序:将有向图中的顶点以线性方式进行排序。即对于任何连接自顶点u到顶点v的有向边uv,在最后的排序结果中,顶点u总是在顶点v的前面。

精髓(具体方法):
⑴ 从图中选择一个入度为0的点加入拓扑序列。
⑵ 从图中删除该结点以及它的所有出边(即与之相邻点入度减1)。
反复执行这两个步骤,直到所有结点都已经进入拓扑序列。

#include<queue>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#define N 2000
using namespace std;
priority_queue<int,vector<int>,greater<int> >q;
int x,s,tot,n,m,sum,ans[N],in[N],head[N];
int read()
{
    int x=0,f=1; char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1; ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0'; ch=getchar();}
    return x*f;
}
struct Edge
{
    int from,to,next;
}edge[N];
int add(int x,int y)
{
    tot++;
    edge[tot].to=y;
    edge[tot].next=head[x];
    head[x]=tot;
}
int main()
{
    freopen("curriculum.in","r",stdin);
    freopen("curriculum.out","w",stdout);
    n=read();
    for(int i=1;i<=n;i++)
    {
        m=read();
        for(int j=1;j<=m;j++)
         x=read(),add(x,i),in[i]++; 
    }
    for(int i=1;i<=n;i++)
      if(in[i]==0) q.push(i);
    while(!q.empty())
    {
        x=q.top(),q.pop();
        sum++;ans[++s]=x;
        for(int i=head[x];i;i=edge[i].next)
        {
            int t=edge[i].to;
            in[t]--; 
            if(in[t]==0) q.push(t);
        } 
    }
    if(sum!=n) printf("no\n");
    else
    {
        for(int i=1;i<=s;i++)
           printf("%d ",ans[i]);
    }
    return 0;
}
字典序最小的拓扑排序

拓扑排序可以用来判环,当我们拓扑排序进行删边的时候若删去的边小于我们本应有的n-1条边,那么就说明我们的图里出现了环

1.我们给出一系列(大小、父子、、、)关系,让我们将这些数进行排序,这个时候大多用拓扑排序。

2.我们要执行一项任务,但在此之前我们要执行完另一些任务,求完成任务的顺序,大多用拓扑排序

                      六、二分图

一、匈牙利算法

http://www.cnblogs.com/z360/p/7103259.html

几个关系:

最小顶点覆盖是指最少的顶点数使得二分图G中的每条边都至少与其中一个点相关联,二分图的最小顶点覆盖数=二分图的最大匹配数;

最小路径(边)覆盖是指用尽量少的不相交简单路径覆盖二分图中的所有顶点。二分图的最小路径覆盖数=|V|-二分图的最大匹配数    

最大独立集(指寻找一个点集,使得其中任意两点在图中无对应边)=|V|-二分图的最大匹配数

讲解就不粘了,有兴趣的可以去我上面的博客里看

简介:

设G=(V,E)是一个无向图。如顶点集V可分割为两个互不相交的子集V1,V2之

选择这样的子集中边数最大的子集称为图的最大匹配问题,如果一个匹配中,|V1|<=|V2|且匹配数|M|=|V1|则称此匹配为完全匹配,也称作完备匹配。特别的当|V1|=|V2|称为完美匹配。

直接看代码:(二分图的最大匹配)

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#define N 1010
using namespace std;
bool vis[N];
int n1,n2,m,x,y,ans,girl[N],map[N][N];
int read()
{
    int x=0,f=1; char ch=getchar();
    while(ch<'0'||ch>'9') {if(ch=='-') f=-1; ch=getchar();}
    while(ch<='9'&&ch>='0'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
int find(int x)
{
    for(int i=1;i<=n2;i++)
     if(!vis[i]&&map[x][i])
     {
         vis[i]=true;
        if(!girl[i]||find(girl[i])){girl[i]=x; return 1;}
     }
    return 0;
}
int main()
{
    n1=read(),n2=read(),m=read();
    for(int i=1;i<=m;i++)
      x=read(),y=read(),map[x][y]=1;
    for(int i=1;i<=n1;i++)
    {
        memset(vis,0,sizeof(vis));
        ans+=find(i);
    }
    printf("%d",ans);
    return 0;
}
邻接矩阵

 行列匹配:(几种情况)(大多都是将行看成一个集合将列看成一个集合)

1.即要行不要列。在同一行列上不能同时出现两次某些东西,那么就需要开两个数组,来分别保存某些可能产生矛盾的点(所谓矛盾点就是可放的点,但是会与其他可放的点在同一行列上而产生矛盾)的坐标,即记录行需要一个数组,列也需要一个数组。

 2.当前放的点会与一些邻近的的点产生矛盾(视题目而定,如hdu 3360)。那么开一个数组记录下原图需要标号的那些点,然后根据矛盾建边即可。

二分图的最大权匹配:http://www.cnblogs.com/z360/p/7113231.html

二分图题集:http://blog.csdn.net/creat2012/article/details/18094703

小总结:我们在做题时大多数会遇到这样两种情况:

1.两个集合里的东西是相互喜欢(可以相互匹配)的,我们如果要求最多可以满足的个数,这个时候我们大多用最大匹配来解决

2.两个集合里的东西实现互矛盾的(选了这个便不能选那个),我们要求最多可以满足的个数,这个时候我们大多用最大独立集

二、二分图染色

http://www.cnblogs.com/z360/p/6954002.html

思想和步骤:我们任意取出一个顶点进行染色,该点和相邻的点有3种情况:1.未染色,这样我们就继续染色它的相邻点(染为另一种颜色)

2.已染色 在这种情况下我们判断其相邻点染得色与该点是否相同。若不同,跳过该点。

3。已染色 且其相邻点的颜色与该点的颜色相同 返回FALSE (即改图不是二分图)

#include<queue>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#define N 1001
#define maxn 9999999
using namespace std;
int n,tot,sum1,sum2,head[N],a[N];
int col[N],que[N],t,h,s1[N],s2[N],f[N];
char q[N],p[N],flag;
struct Edge
{
    int from,to,next;    
}edge[N];
void add(int from,int to)
{
    ++tot;
    edge[tot].to=to;
    edge[tot].next=head[from];
    head[from]=tot;
}
void paint(int s)
{
    queue<int> q;
    q.push(s);
    col[s]=0;//我们把放在第一个栈中的颜色染成0  
    if(flag) return ;
    while(!q.empty())
    {
        int x=q.front();
        for(int j=head[x];j;j=edge[j].next)
        {
            if(col[edge[j].to]!=-1)//说明已被染过色 
            {
                if(col[edge[j].to]==col[x])//并且染的色与该点相同,这样就说明其不是二分图 
                  flag=true;
            }
            else
            {
                col[edge[j].to]=col[x]^1;//用另一种颜色染其相邻的点 
                q.push(edge[j].to);
            }
        }
        q.pop();
    }
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)    scanf("%d",&a[i]);
    f[n+1]=maxn;
    for(int i=n;i>=1;i--)
      f[i]=min(f[i+1],a[i]);//预处理从每一个点出发到序列末端的最小值,也可以用RMQ 
    for(int i=1;i<=n;i++)//我们挨个判断不能让在同一个栈中的点,对其进行连边,枚举序列内起点 
     for(int j=i+1;j<=n;j++)//由于我们在这两步中枚举的是A数组的下标,我们又要找不能放在一个栈中的情况
     //在前面我们有说到:对于i<j<k&&a[K]<a[i]<a[j]的情况是一定不能放在同一个栈中的 
      if(a[i]>f[j+1]&&a[i]<a[j])//在这里我们就是找上述的情况 
       add(i,j),add(j,i);//不能放在一个栈中,连边 
    memset(col,-1,sizeof(col));//初始化col数组,方便我们判断与一个点相连的点是否被染过色。 
   for(int i=1;i<=n;i++)//在这里我们不一定把她连成整的一棵树,所以我们要循环枚举所有相连的节点,把它全部染色 
    if(col[i]==-1)
     paint(i);//染色 
    if(flag) {printf("0\n");return 0;}//不能用两个栈进行排序
    int now=1;//我们要把给定的序列a排成有序的序列,并且所给的数构可构成一个1~n的排列
    for(int i=1;i<=n;i++)
    {
        if(col[i]==0)
        {
            if(a[i]==now) printf("a b "),now++;
            else  
            {
                while(s1[sum1]==now) sum1--,printf("b "),now++;
                s1[++sum1]=a[i],printf("a ");
            }
        }
        else
        {
            if(a[i]==now) printf("c d "),now++;
            else 
            {
                while(s1[sum1]==now) sum1--,printf("b "),now++;//注意:你在这里出栈的时候,对应的序列也已经排完了一个 
                s2[++sum2]=a[i],printf("c ");
            }
        }
    }
    for(int i=now;i<=n;i++)
    {
        while(i==s1[sum1]&&sum1) sum1--,printf("b ");
        while(i==s2[sum2]&&sum2) sum2--,printf("d ");
    }
    return  0;
}
双栈排序(例题)

 三、带权二分图

http://www.cnblogs.com/z360/p/7113231.html

算法流程

(1)初始化可行顶标的值

(2)用匈牙利算法寻找完备匹配

(3)若未找到完备匹配则修改可行顶标的值

(4)重复(2)(3)直到找到相等子图的完备匹配为止 

小总结:

KM算法是用来解决最大权匹配问题的,在一个二分图里,左顶点为x,右顶点为y,现对于每组左右连接连接XiYj有权wij,求一种匹配使得所有wij的和最大。

也就是说,最大匹配一定是完美匹配。(完美匹配:二分图的两边点数相同)

如果点数不相同,我们可以通过虚拟点权为0的点来时的点数相同,这也就使这个图变成了完美匹配。

#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std;
const int MAXN = 305;
const int INF = 0x3f3f3f3f;
int love[MAXN][MAXN];   // 记录每个妹子和每个男生的好感度
int ex_girl[MAXN];      // 每个妹子的期望值
int ex_boy[MAXN];       // 每个男生的期望值
bool vis_girl[MAXN];    // 记录每一轮匹配匹配过的女生
bool vis_boy[MAXN];     // 记录每一轮匹配匹配过的男生
int match[MAXN];        // 记录每个男生匹配到的妹子 如果没有则为-1
int slack[MAXN];        // 记录每个汉子如果能被妹子倾心最少还需要多少期望值
int N;
bool dfs(int girl)
{
    vis_girl[girl] = true;
    for (int boy = 0; boy < N; ++boy) {
        if (vis_boy[boy]) continue; // 每一轮匹配 每个男生只尝试一次
        int gap = ex_girl[girl] + ex_boy[boy] - love[girl][boy];
        if (gap == 0) {  // 如果符合要求
            vis_boy[boy] = true;
            if (match[boy] == -1 || dfs( match[boy] )) {    // 找到一个没有匹配的男生 或者该男生的妹子可以找到其他人
                match[boy] = girl;
                return true;
            }
        } else {
            slack[boy] = min(slack[boy], gap);  // slack 可以理解为该男生要得到女生的倾心 还需多少期望值 取最小值 备胎的样子【捂脸
        }
    }
    return false;
}
int KM()
{
    memset(match, -1, sizeof match);    // 初始每个男生都没有匹配的女生
    memset(ex_boy, 0, sizeof ex_boy);   // 初始每个男生的期望值为0
    // 每个女生的初始期望值是与她相连的男生最大的好感度
    for (int i = 0; i < N; ++i) 
    {
        ex_girl[i] = love[i][0];
        for (int j = 1; j < N; ++j) 
            ex_girl[i] = max(ex_girl[i], love[i][j]);
    }
    // 尝试为每一个女生解决归宿问题
    for (int i = 0; i < N; ++i) 
    {
        fill(slack, slack + N, INF);    // 因为要取最小值 初始化为无穷大
        while (1) 
        {
            // 为每个女生解决归宿问题的方法是 :如果找不到就降低期望值,直到找到为止
            // 记录每轮匹配中男生女生是否被尝试匹配过
            memset(vis_girl, false, sizeof vis_girl);
            memset(vis_boy, false, sizeof vis_boy);
            if (dfs(i)) break;  // 找到归宿 退出
            // 如果不能找到 就降低期望值
            // 最小可降低的期望值
            int d = INF;
            for (int j = 0; j < N; ++j)
                if (!vis_boy[j]) d = min(d, slack[j]);
            for (int j = 0; j < N; ++j) {
                // 所有访问过的女生降低期望值
                if (vis_girl[j]) ex_girl[j] -= d;
                // 所有访问过的男生增加期望值
                if (vis_boy[j]) ex_boy[j] += d;
                // 没有访问过的boy 因为girl们的期望值降低,距离得到女生倾心又进了一步!
                else slack[j] -= d;
            }
        }
    }
    // 匹配完成 求出所有配对的好感度的和
    int res = 0;
    for (int i = 0; i < N; ++i)
        res += love[ match[i] ][i];
    return res;
}
int main()
{
    while (~scanf("%d", &N)) {
        for (int i = 0; i < N; ++i)
            for (int j = 0; j < N; ++j)
                scanf("%d", &love[i][j]);
        printf("%d\n", KM());
    }
    return 0;
}
KM算法

 

                      七、lca

http://www.cnblogs.com/z360/p/6822658.html

 1.倍增:

倍增法就是我们先把深度不同的两个点转化成深度相同的点。然后再对这两个点同时倍增。

这种做法我们先用一个数组fa[x]【y】数组来存第x个节点的2^y的父亲节点。

这样我们就能在o(lg n)的时间内查询任意一个点的lca。

所以我们还是采用上面所述的那种做法,现将深度不同的两个点转化成深度相同的两个点。

然后再对两个点同时进行倍增。

 

#include<vector>
#include<stdio.h>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#define N 500001
#define maxn 123456
using namespace std;
vector<int>vec[N];
int n,x,y,fa[N][20],deep[N],m,root;
void dfs(int x)
{
    deep[x]=deep[fa[x][0]]+1;
    for(int i=0;fa[x][i];i++)
      fa[x][i+1]=fa[fa[x][i]][i];
    for(int i=0;i<vec[x].size();i++)
    {
        if(!deep[vec[x][i]])
        {
            fa[vec[x][i]][0]=x;
            dfs(vec[x][i]);
         } 
    }
}
int lca(int x,int y)
{
    if(deep[x]>deep[y])
      swap(x,y);//省下后面进行分类讨论,比较方便 
    for(int i=18;i>=0;i--)
    {
        if(deep[fa[y][i]]>=deep[x]) 
          y=fa[y][i];//让一个点进行倍增,直到这两个点的深度相同 
    }
    if(x==y) return x;//判断两个点在一条链上的情况 
    for(int i=18;i>=0;i--)
    {
        if(fa[x][i]!=fa[y][i])
        {
            y=fa[y][i];
            x=fa[x][i];
          }  
    }
    return fa[x][0];//这样两点的父亲就是他们的最近公共祖先 
}
int main()
{
    scanf("%d%d%d",&n,&m,&root);
    for(int i=1;i<n;i++)
    {
        scanf("%d%d",&x,&y);
        vec[x].push_back(y);
        vec[y].push_back(x);
    }
    deep[root]=1;
    dfs(root);
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d",&x,&y);
        printf("%d\n",lca(x,y));
    }
    return 0;
}
倍增

 

 2.tarjan(离线算法、、、)

原理:Tarjan 算法建立在 DFS 的基础上.
假如我们正在遍历节点 x, 那么根据所有节点各自与 x 的 LCA 是谁, 我们可以将节点进行分类: x 与 x 的兄弟节点的 LCA 是 x 的父亲, x 与 x 的父亲的兄弟节点的 LCA 是 x 的父亲的父亲, x 与
x 的父亲的父亲的兄弟节点的 LCA 是 x 的父亲的父亲的父亲... 将这些类别各自归入不同的集合中, 如果我们能够维护好这些集合, 就能够很轻松地处理有关 x 节点的 LCA 的询问. 显然我们可以使用 并查集来维护.

 

我们需要将米一组询问用vec储存下来,将其挂在改组询问询问的两个节点上。

之后遍历整棵树。在访问一个节点x时,我们设置这个节点的fa【x】=x;只有在询问完时,我们将这个点的fa【x】设置城dad【x】。

之后我们在询问一个节点时,枚举过于这个点的所有询问。若果询问中的另一个节点已经被访问过,那么这两个点的lca就是已经被访问过的这个点。(哼,蒟蒻用tarjan做了一个题,md几乎是调了半天,哼,不是MLE就是TLE,哼,以后再也不用tarjan了!!!!)

 

#include<vector>
#include<stdio.h>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#define N 500001
using namespace std;
vector<int>vec[N],que[N];
int n,m,qx[N],qy[N],x,y,root,fa[N],dad[N],ans[N];
int find(int x)
{
    return fa[x]==x?x:fa[x]=find(fa[x]);
}
void dfs(int x)
{
    fa[x]=x;
    for(int i=0;i<vec[x].size();i++)
     if(vec[x][i]!=dad[x])
      dad[vec[x][i]]=x,dfs(vec[x][i]);
    for(int i=0;i<que[x].size();i++)
      if(dad[y=qx[que[x][i]]^qy[que[x][i]]^x])
       ans[que[x][i]]=find(y);
    fa[x]=dad[x];
}
int main()
{
    scanf("%d%d%d",&n,&m,&root);
    for(int i=1;i<n;i++)
    {
        scanf("%d%d",&x,&y);
        vec[x].push_back(y);
        vec[y].push_back(x);
    }
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d",&qx[i],&qy[i]);
        que[qx[i]].push_back(i);
        que[qy[i]].push_back(i);
    }
    dfs(root);
    for(int i=1;i<=m;i++)
     printf("%d\n",ans[i]);
    return 0;
 }
tarjan

 

 3.树剖

用树链剖分来求两节点x,y的lca首先我们要先判断两个节点的top那个大,我们把top值小的节点改成fa[top[x]]

重复上述两个过程直到top[x]=top[y],最后将上述两个节点中深度较小的值作为这两点的lca。

#include<vector>
#include<stdio.h>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#define N 500001
#define maxn 123456
using namespace std;
vector<int>vec[N];
int n,m,root,x,y,fa[N],deep[N],size[N],top[N];
int lca(int x,int y)
{
    for( ;top[x]!=top[y];)
    {
        if(deep[top[x]]<deep[top[y]])
         swap(x,y);
        x=fa[x];
    }
    if(deep[x]>deep[y])
     swap(x,y);
    return x;
 } 
void dfs(int x)
{
    size[x]=1;
    deep[x]=deep[fa[x]]+1;
    for(int i=0;i<vec[x].size();i++)
    {
        if(fa[x]!=vec[x][i])
        {
            fa[vec[x][i]]=x;
            dfs(vec[x][i]);
            size[x]+=size[vec[x][i]];
        }
    }
}
void dfs1(int x)
{
    int t=0;
    if(!top[x])  top[x]=x;
    for(int i=0;i<vec[x].size();i++)
      if(vec[x][i]!=fa[x]&&size[vec[x][i]]>size[t])
         t=vec[x][i];
    if(t) 
    {
        top[t]=top[x];
        dfs1(t);
    }
    for(int i=0;i<vec[x].size();i++)
     if(vec[x][i]!=fa[x]&&vec[x][i]!=t)
      dfs1(vec[x][i]);
}
int main()
{    scanf("%d%d%d",&n,&m,&root);
    for(int i=1;i<n;i++)
    {
        scanf("%d%d",&x,&y);
        vec[x].push_back(y);
        vec[y].push_back(x);
    }
    dfs(root);
    dfs1(root);
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d",&x,&y);
        printf("%d\n",lca(x,y));
    }
    return 0;
}
树剖

 

做题小方法、、、

(看lca时可以看这个博客http://www.cnblogs.com/scau20110726/archive/2013/06/14/3135095.html

①我们求解以下类似问题时若数据太大,直接求lca一定超时,那么我们可以用下面方法:

问题:给定一棵有n个节点的有根树,根节点为1,每个节点有一个权值wi,求

即求所有无序节点对的LCA的权值之和。

解决方法:http://www.cnblogs.com/z360/p/7405849.html

 

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#define N 1000100
using namespace std;
int n,x,y,tot;
long long w[N],fa[N],sum[N],ans,head[N];
int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1; ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0'; ch=getchar();}
    return x*f;
}
struct Edge
{
    int to,next;
}edge[N<<1];
int add(int x,int y)
{
    tot++;
    edge[tot].to=y;
    edge[tot].next=head[x];
    head[x]=tot;
}
int dfs(int x)
{
    sum[x]=1;
    for(int i=head[x];i;i=edge[i].next)
    {
        int to=edge[i].to;
        if(to!=fa[x])
        {
            fa[to]=x;dfs(to);
            ans+=(long long)sum[x]*sum[to]*(long long)w[x];
            sum[x]+=(long long)sum[to];
        }
    }
}
int main()
{
    freopen("easy_LCA.in","r",stdin);
    freopen("easy_LCA.out","w",stdout);
    n=read();
    for(int i=1;i<=n;i++) w[i]=read();
    for(int i=1;i<n;i++)
    {
        x=read(),y=read();
        add(x,y);add(y,x);
    }
    dfs(1);
    for(int i=1;i<=n;i++) ans+=(long long)w[i];
    printf("%lld",ans);
    return 0;
}
乱搞

 

 ②我们求解树上的两条路径设为(x,y)(xx,yy)是否有交点时,可以分别求lca(x,y),lca(xx,yy)然后在判断lca(x,y)的深度与lca(xx,yy)的深度,用深度深的那个lca,设为lca1吧,我们判断lca1与xx或yy的lca只要为另一个lca那么着两条路径就有交点。

具体题目:http://www.cnblogs.com/z360/p/7411760.html

 

#include<cstdio>
#include<vector>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#define N 1100
using namespace std;
vector<int>vec[N];
int n,m,x,y,q,p,root;
int fa[N],top[N],deep[N],size[N];
int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1; ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0'; ch=getchar();}
    return x*f;
}
int lca(int x,int y)
{
    for(;top[x]!=top[y];)
    {
        if(deep[top[x]]<deep[top[y]])
         swap(x,y);
        x=fa[x];
    }
    if(deep[x]>deep[y])
     swap(x,y);
    return x;
}
int dfs(int x)
{
    size[x]=1;
    deep[x]=deep[fa[x]]+1;
    for(int i=0;i<vec[x].size();i++)
     if(vec[x][i]!=fa[x])
     {
         fa[vec[x][i]]=x;
         dfs(vec[x][i]);
         size[x]+=size[vec[x][i]];
     }
}
int dfs1(int x)
{
    int t=0;
    if(!top[x]) top[x]=x;
    for(int i=0;i<vec[x].size();i++)
     if(vec[x][i]!=fa[x]&&size[vec[x][i]]>size[t])
      t=vec[x][i];
    if(t) top[t]=top[x],dfs1(t);
    for(int i=0;i<vec[x].size();i++)
      if(vec[x][i]!=fa[x]&&vec[x][i]!=t)
       dfs1(vec[x][i]);
}
int main()
{
    freopen("asm_virus.in","r",stdin);
    freopen("asm_virus.out","w",stdout);
    n=read(),m=read();
    for(int i=1;i<n;i++)
    {
        x=read(),y=read();fa[y]=x;
        vec[x].push_back(y);
        vec[y].push_back(x);
    }
    dfs(1),dfs1(1);
    for(int i=1;i<=m;i++)
    {
        x=read(),y=read(),q=read(),p=read();
        int Lca=lca(x,y),LCA=lca(q,p);
        //printf("%d %d\n",Lca,LCA);
        if(deep[Lca]<deep[LCA])
         swap(Lca,LCA),swap(x,q),swap(y,p);
        if(lca(Lca,q)==Lca||lca(Lca,p)==Lca) printf("YES\n");
        else printf("NO\n");
    }
    return 0;
}
View Code

 

③。我们求解树上两个节点的最短距离时可以通过求两节点lca来解决,ans=dis[x]+dis[y]-2*dis[lca(x,y)]

具体题目:http://www.cnblogs.com/z360/p/7411951.html

 

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#define N 51000
using namespace std;
int n,m,x,y,z,t,tot,ans;
int fa[N],dis[N],top[N],deep[N],size[N],head[N];
struct Edge
{
    int from,to,dis,next;
}edge[N<<1];
int read()
{
    int x=0,f=1; char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1; ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0'; ch=getchar();}
    return x*f;
}
int add(int x,int y,int z)
{
    tot++;
    edge[tot].to=y;
    edge[tot].dis=z;
    edge[tot].next=head[x];
    head[x]=tot;
}
int dfs(int x)
{
    size[x]=1;
    deep[x]=deep[fa[x]]+1;
    for(int i=head[x];i;i=edge[i].next)
    {
        int to=edge[i].to;
        if(fa[x]==to) continue;
        dis[to]=dis[x]+edge[i].dis;
        fa[to]=x,dfs(to);size[x]+=size[to];
    }
}
int dfs1(int x)
{
    int t=0;
    if(!top[x]) top[x]=x;
    for(int i=head[x];i;i=edge[i].next)
    {
        int to=edge[i].to;
        if(fa[x]!=to&&size[t]<size[to]) t=to;
    }
    if(t) top[t]=top[x],dfs1(t);
    for(int i=head[x];i;i=edge[i].next)
    {
        int to=edge[i].to;
        if(fa[x]!=to&&to!=t)  dfs1(to);
    }
}
int lca(int x,int y)
{
    for(;top[x]!=top[y];x=fa[top[x]])
     if(deep[top[x]]<deep[top[y]]) 
      swap(x,y);
    if(deep[x]>deep[y]) swap(x,y);
    return x;
}
int main()
{
    n=read(),m=read();
    for(int i=1;i<=m;i++)
    {
        x=read(),y=read(),z=read();
        add(x,y,z),add(y,x,z);
    }
    dfs(1),dfs1(1);
    t=read();
    for(int i=1;i<=t;i++)
    {
        x=read(),y=read();
        ans=dis[x]+dis[y]-2*dis[lca(x,y)];
        printf("%d\n",ans);
    }
    return 0;
}
两点间最短距离

 

 同样,求3个点之间的最短距离课题通过求这三个点两两间的最短距离,相加再除以二就好了、、、

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#define N 51000
using namespace std;
int n,m,x,y,z,ans,sum,tot,ans1,ans2,ans3;
int fa[N],top[N],size[N],deep[N],head[N],dis[N];
struct Edge
{
    int to,dis,from,next;
}edge[N<<1];
int add(int x,int y,int z)
{
    tot++;
    edge[tot].to=y;
    edge[tot].dis=z;
    edge[tot].next=head[x];
    head[x]=tot;
}
int read()
{
    int x=0,f=1; char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1; ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0'; ch=getchar();}
    return x*f;
}
int lca(int x,int y)
{
    for(;top[x]!=top[y];x=fa[top[x]])
      if(deep[top[x]]<deep[top[y]])
        swap(x,y);
    if(deep[x]>deep[y]) swap(x,y);
    return x;
}
int dfs(int x)
{
    size[x]=1;
    deep[x]=deep[fa[x]]+1;
    for(int i=head[x];i;i=edge[i].next)
    {
        int to=edge[i].to;
        if(fa[x]!=to)
        {
            dis[to]=dis[x]+edge[i].dis;
            fa[to]=x,dfs(to);
            size[x]+=size[to];
        }
    }
}
int dfs1(int x)
{
    int t=0;
    if(!top[x]) top[x]=x;
    for(int i=head[x];i;i=edge[i].next)
    {
        int to=edge[i].to;
        if(fa[x]!=to&&size[t]<size[to]) t=to;
     } 
     if(t) top[t]=top[x],dfs1(t);
     for(int i=head[x];i;i=edge[i].next)
     {
         int to=edge[i].to;
         if(fa[x]!=to&&to!=t) dfs1(to);
     }
}
int begin()
{
    ans=0;tot=0;
    memset(fa,0,sizeof(fa));
    memset(top,0,sizeof(top));
    memset(dis,0,sizeof(dis));
    memset(edge,0,sizeof(edge));
    memset(deep,0,sizeof(deep));
    memset(size,0,sizeof(size));
    memset(head,0,sizeof(head));
}
int main()
{
    while(scanf("%d",&n)!=EOF)
    {
        if(sum++) printf("\n");
        begin();
        for(int i=1;i<n;i++)
        {
            x=read(),y=read(),z=read();
            add(x,y,z),add(y,x,z);
        }
        dfs(0),dfs1(0);
        m=read();
        for(int i=1;i<=m;i++)
        {
            x=read(),y=read(),z=read();
            ans1=dis[x]+dis[y]-2*dis[lca(x,y)];
            ans2=dis[x]+dis[z]-2*dis[lca(x,z)];
            ans3=dis[y]+dis[z]-2*dis[lca(y,z)];
            ans=(ans1+ans2+ans3)/2;
            printf("%d\n",ans);
        }
    }
    return 0;
}
三点间最短距离

     

 

转载于:https://www.cnblogs.com/z360/p/7363034.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值