图论模板二

本文深入讲解图算法,包括SPFA、Bellman-Ford、拓扑排序、树的直径、强连通分量、边双连通性和缩点技巧。通过实例解析,探讨如何判断负权环、求解最短路径、识别拓扑排序可能性、计算树直径、检测强连通性和边双连通性,以及如何通过缩点简化复杂图结构。

SPFA

题意:

        给你一个有向图,要你判断图中是否存在负权环.

分析:

        建图,然后用Bellman_Ford判负圈的模板解即可.

        注意负权的边是单向的,其他边是双向的.

        注意刘汝佳的模板计算的是单源最短路径,我们如果想判断是否有负权环,需要添加一个超级源0号点.

        如程序下面代码所示,添加0号顶点作为到达其他所有顶点的超级源:


for(int i=1;i<=n;i++)  
            BF.AddEdge(0,i,0); 

#include<cstdio>
#include<cstring>
#include<queue>
#include<vector>
#include<algorithm>
#define INF 1e9
using namespace std;
const int maxn=500+10;
 
struct Edge
{
    int from,to,dist;
    Edge(int f,int t,int d):from(f),to(t),dist(d){}
};
 
struct BellmanFord
{
    int n,m;
    vector<Edge> edges;
    vector<int> G[maxn];
    bool inq[maxn];
    int cnt[maxn];
    int d[maxn];
    int p[maxn];
 
    void init(int n)
    {
        this->n=n;
        for(int i=0;i<n;i++) G[i].clear();
        edges.clear();
    }
 
    void AddEdge(int from,int to,int dist)
    {
        edges.push_back(Edge(from,to,dist));
        m=edges.size();
        G[from].push_back(m-1);
    }
 
    bool negative_cycle()
    {
        memset(inq,0,sizeof(inq));
        memset(cnt,0,sizeof(cnt));
        queue<int> Q;
        for(int i=0;i<n;i++) d[i]= i==0?0:INF;
        Q.push(0);
 
        while(!Q.empty())
        {
            int u =Q.front(); Q.pop();
            inq[u]=false;
            for(int i=0;i<G[u].size();i++)
            {
                Edge &e=edges[G[u][i]];
                if(d[e.to]> d[u]+e.dist)
                {
                    d[e.to] = d[u]+e.dist;
                    p[e.to] = G[u][i];
                    if(!inq[e.to])
                    {
                        inq[e.to]=true;
                        Q.push(e.to);
                        if(++cnt[e.to]>n) return true;
                    }
                }
            }
        }
        return false;
    }
}BF;
 
int main()
{
    int T; scanf("%d",&T);
    while(T--)
    {
        int n,m,w;
        scanf("%d%d%d",&n,&m,&w);
        BF.init(n+1);
        while(m--)
        {
            int u,v,d;
            scanf("%d%d%d",&u,&v,&d);
            BF.AddEdge(u,v,d);
            BF.AddEdge(v,u,d);
        }
        while(w--)
        {
            int u,v,d;
            scanf("%d%d%d",&u,&v,&d);
            BF.AddEdge(u,v,-d);
        }
        for(int i=1;i<=n;i++)
            BF.AddEdge(0,i,0);
        if(BF.negative_cycle()) printf("YES\n");
        else printf("NO\n");
    }
    return 0;
}

1-N最短路+反向建图

题意:

        有一个N个点的有向图,点的编号从1到N.现在要你求从1号点到所有其他点的最短距离S1+ 从所有其他点到1号点的最短距离S2的值.

分析:

        求其他点到1号点的最短距离直接建立原图的反向图然后求最短路径即可.

        直接利用SPFA求解.注意最终结果ans要用long long.

        注意:此题数据高达100W,所以如果用带vector的SPFA就会超时,这里只能用我们自己实现的邻接表来保存边的信息.具体看代码.

AC代码:


#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
#define INF 1e9
using namespace std;
const int maxn =1000000+10;
int n,m;
 
struct Edge
{
    int from,to,dist;
    Edge(){}
    Edge(int f,int t,int d):from(f),to(t),dist(d){}
};
 
struct BellmanFord
{
    int n,m;
    int head[maxn];     //每个节点邻接表的头
    int next[maxn];
    Edge edges[maxn];   //所有的边信息
    bool inq[maxn];
    int d[maxn];
    int p[maxn];
    int cnt[maxn];
 
    void init(int n)
    {
        this->n=n;
        this->m=0;
        memset(head,-1,sizeof(head));
    }
 
    void AddEdge(int from,int to,int dist)
    {
        edges[m]=Edge(from,to,dist);
        next[m]=head[from];
        head[from] = m++;
    }
 
    bool negativeCycle(int s)
    {
        queue<int> Q;
        memset(inq,0,sizeof(inq));
        memset(cnt,0,sizeof(cnt));
        for(int i=0;i<n;i++) d[i]= i==s?0:INF;
        Q.push(s);
 
        while(!Q.empty())
        {
            int u=Q.front(); Q.pop();
            inq[u]=false;
            for(int i=head[u];i!=-1;i=next[i])
            {
                Edge &e=edges[i];
                if(d[e.to] > d[u]+e.dist)
                {
                    d[e.to] = d[u]+e.dist;
                    p[e.to] = i;
                    if(!inq[e.to])
                    {
                        inq[e.to]=true;
                        Q.push(e.to);
                        if(++cnt[e.to]>n) return true;
                    }
                }
            }
        }
        return false;
    }
}BF1,BF2;
 
int main()
{
    int T; scanf("%d",&T);
    while(T--)
    {
        scanf("%d%d",&n,&m);
        BF1.init(n), BF2.init(n);
 
        while(m--)
        {
            int u,v,d;
            scanf("%d%d%d",&u,&v,&d);
            u--,v--;
            BF1.AddEdge(u,v,d);
            BF2.AddEdge(v,u,d);
        }
        BF1.negativeCycle(0);
        BF2.negativeCycle(0);
 
        long long ans=0;
        for(int i=1;i<n;i++)
            ans += BF1.d[i]+BF2.d[i];
        printf("%I64d\n",ans);
    }
    return 0;
}
递增环

题意:

        给定你N种货币,以及M对特定货币之间的对换比率.现在你手上有货币S,问你能否通过不断地对换然后增加自己S货币的总量?

分析:

        首先我们要知道只要是从货币S出发的路径上存在一个正的环,那么一定能使得初始货币S的数量无限大.

        BellmanFord求得是最短路,这里我们需要求最长路.所以我们令d[i]表示从val个S货币能换取的最多的i货币数目.d[i]初值为0,d[S]=val.

        松弛的情况:当 d[i]< d[j]*(1-commissions)*rate,那么就更新d[i],且要让i入队列.

        那么当有节点入队次数>n时,就说明图中肯定存在从s出发能到达的递增环了。

AC代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
using namespace std;
const int maxn = 100+10;
 
struct Edge
{
    int from,to;
    double r,c;
    Edge(int f,int t,double r,double c):from(f),to(t),r(r),c(c){}
};
 
struct BellmanFord
{
    int n,m;
    vector<Edge> edges;
    vector<int> G[maxn];
    bool inq[maxn];
    int cnt[maxn];
    double d[maxn];
 
    void init(int n)
    {
        this->n=n;
        for(int i=0;i<n;i++) G[i].clear();
        edges.clear();
    }
 
    void AddEdge(int from,int to,double r,double c)
    {
        edges.push_back(Edge(from,to,r,c));
        m = edges.size();
        G[from].push_back(m-1);
    }
 
    bool bellman_ford(int s,double val)
    {
        memset(inq,0,sizeof(inq));
        memset(cnt,0,sizeof(cnt));
        for(int i=0;i<n;i++) d[i]= i==s?val:0;
        queue<int> Q;
        Q.push(s);
 
        while(!Q.empty())
        {
            int u=Q.front(); Q.pop();
            inq[u]=false;
            for(int i=0;i<G[u].size();i++)
            {
                Edge &e=edges[G[u][i]];
                if(d[e.to] < (d[u]-e.c)*e.r)//这里的松弛条件变了
                {
                    d[e.to] = (d[u]-e.c)*e.r;
                    if(!inq[e.to])
                    {
                        inq[e.to]=true;
                        Q.push(e.to);
                        if(++cnt[e.to]>n) return true;
                    }
                }
            }
        }
        return false;
    }
}BF;
 
int main()
{
    int n,m,s;
    double v;
    while(scanf("%d%d%d%lf",&n,&m,&s,&v)==4)
    {
        s--;
        BF.init(n);
        while(m--)
        {
            int u,v;
            double r1,c1,r2,c2;
            scanf("%d%d%lf%lf%lf%lf",&u,&v,&r1,&c1,&r2,&c2);
            u--,v--;
            BF.AddEdge(u,v,r1,c1);
            BF.AddEdge(v,u,r2,c2);
        }
        printf("%s\n",BF.bellman_ford(s,v)?"YES":"NO");
    }
    return 0;
}
模板

//Bellman_Ford标准版模板_SPFA(能判负圈)
//求的是从s点到其他点的单源最短路径,复杂度O(n*m)
 
#include<cstdio>
#include<cstring>
#include<queue>
#include<vector>
#include<algorithm>
using namespace std;
#define INF 1e9
 
struct Edge
{
    int from,to,dist;
    Edge(int f,int t,int d):from(f),to(t),dist(d){}
};
 
struct BellmanFord
{
    int n,m;            //点数和边数,编号都从0开始
    vector<Edge> edges; //边列表
    vector<int> G[maxn];//每个节点出发的边编号(从0开始编号)
    bool inq[maxn];     //是否在队列中
    int d[maxn];        //s到各个点的距离
    int p[maxn];        //最短路中的上一条弧
    int cnt[maxn];      //进队次数
 
    void init(int n)
    {
        this->n=n;
        for(int i=0;i<n;i++) G[i].clear();
        edges.clear();
    }
 
    void AddEdge(int from,int to,int dist)
    {
        edges.push_back(Edge(from,to,dist));
        m = edges.size();
        G[from].push_back(m-1);
    }
 
    //计算以s为源点的最短路径
    //如果图中存在s能到达的负圈,那么返回true
    bool negativeCycle(int s)
    {
        queue<int> Q;
        memset(inq,0,sizeof(inq));
        memset(cnt,0,sizeof(cnt));
        for(int i=0;i<n;i++) d[i]= i==s?0:INF;
        Q.push(s);
 
        while(!Q.empty())
        {
            int u=Q.front(); Q.pop();
            inq[u]=false;
            for(int i=0;i<G[u].size();i++)
            {
                Edge &e=edges[G[u][i]];
                if(d[e.to] > d[u]+e.dist)
                {
                    d[e.to] = d[u]+e.dist;
                    p[e.to] = G[u][i];
                    if(!inq[e.to])
                    {
                        Q.push(e.to);
                        inq[e.to]=true;
                        if(++cnt[e.to]>n) return true;
                    }
                }
            }
        }
        return false;
    }
}BF;
强连通分量----------------

题意:

        给你一个有向图,问你在图中最少要加多少条边能使得该图变成一个强连通图.

分析:

        首先我们求出该图的各个强连通分量,然后把每个强连通分量看出一个点(即缩点),然后我们得到了一个有向无环图(DAG).

        对于一个DAG,我们需要添加max(a,b)条边才能使其强连通.其中a为DAG中出度为0的点总数,b为DAG中入度为0的点总数.

        注意特殊情况:如果图已经强连通了,我们需要添加的边是0条,而不是1条.
--------------------- 


#include<cstdio>
#include<cstring>
#include<algorithm>
#include<stack>
#include<vector>
using namespace std;
const int maxn=20000+10;
int n,m;
vector<int> G[maxn];
stack<int> S;
int dfs_clock,scc_cnt;
int pre[maxn],low[maxn],sccno[maxn];
bool in0[maxn],out0[maxn];
void dfs(int u)
{
    pre[u]=low[u]=++dfs_clock;
    S.push(u);
    for(int i=0;i<G[u].size();i++)
    {
        int v=G[u][i];
        if(!pre[v])
        {
            dfs(v);
            low[u]=min(low[u],low[v]);
        }
        else if(!sccno[v])
            low[u]=min(low[u],pre[v]);
    }
    if(low[u]==pre[u])//强连通分量起点
    {
        scc_cnt++;
        while(true)
        {
            int x= S.top(); S.pop();
            sccno[x]=scc_cnt;
            if(x==u) break;
        }
    }
}
void find_scc(int n)
{
    scc_cnt=dfs_clock=0;
    memset(pre,0,sizeof(pre));
    memset(sccno,0,sizeof(sccno));
    for(int i=0;i<n;i++)
        if(!pre[i]) dfs(i);
}
int main()
{
    int T; scanf("%d",&T);
    while(T--)
    {
        scanf("%d%d",&n,&m);
        for(int i=0;i<n;i++) G[i].clear();
        while(m--)
        {
            int u,v;
            scanf("%d%d",&u,&v);
            u--, v--;
            G[u].push_back(v);
        }
        find_scc(n);
        for(int i=1;i<=scc_cnt;i++) in0[i]=out0[i]=true;
        for(int u=0;u<n;u++)
        {
            for(int i=0;i<G[u].size();i++)
            {
                int v=G[u][i];
                if(sccno[u] != sccno[v]) out0[sccno[u]]=in0[sccno[v]]=false;
            }
        }
        int a=0,b=0;
        for(int i=1;i<=scc_cnt;i++)
        {
            if(in0[i]) a++;
            if(out0[i]) b++;
        }
        int ans=max(a,b);
        if(scc_cnt==1) ans=0;
        printf("%d\n",ans);
    }
    return 0;
}
题意:

        网络中的一学校可以将软件发送给其他一些学校,能够发送给谁取决于他们各自维护的一个清单。将学校看成一个节点,给出每个学校的维护清单,问至少需要复制几次软件,使毎个学校都能够得到该软件。然后问在清单中至少添加几项,可使软件只要复制一次,所有学校都可以得到(使得该图强连通)。

分析:

        本题与POJ2767很类似:

http://blog.youkuaiyun.com/u013480600/article/details/31805017

        1.    求出该图的所有强连通分量.

        2.    将每个分量缩点构新DAG图.

        3.    第一问就是新图中入度为0点的个数,第二问是max(入度0点数,出度0点数)

        上面第二问的结论不好证明.第一问的结论自己画个图就能验证出来.


#include<cstdio>
#include<cstring>
#include<vector>
#include<stack>
#include<algorithm>
using namespace std;
const int maxn=100+10;
int n,m;
vector<int> G[maxn];
stack<int> S;
int dfs_clock, scc_cnt;
int pre[maxn],low[maxn],sccno[maxn];
int in0[maxn],out0[maxn];
void dfs(int u)
{
    pre[u]=low[u]=++dfs_clock;
    S.push(u);
    for(int i=0; i<G[u].size(); i++)
    {
        int v=G[u][i];
        if(!pre[v])
        {
            dfs(v);
            low[u]=min(low[u],low[v]);
        }
        else if(!sccno[v])
            low[u]=min(low[u],pre[v]);
    }
    if(low[u]==pre[u])
    {
        scc_cnt++;
        while(true)
        {
            int x=S.top(); S.pop();
            sccno[x]=scc_cnt;
            if(x==u) break;
        }
    }
}
void find_scc(int n)
{
    dfs_clock=scc_cnt=0;
    memset(pre,0,sizeof(pre));
    memset(sccno,0,sizeof(sccno));
    for(int i=0;i<n;i++)
        if(!pre[i]) dfs(i);
}
int main()
{
    while(scanf("%d",&n)==1&&n)
    {
        for(int i=0;i<n;i++) G[i].clear();
        for(int u=0;u<n;u++)
        {
            int v;
            while(scanf("%d",&v)==1&&v)
            {
                v--;
                G[u].push_back(v);
            }
        }
        find_scc(n);
        for(int i=1;i<=scc_cnt;i++)
            in0[i]=out0[i]=true;
        for(int u=0;u<n;u++)
        for(int i=0;i<G[u].size();i++)
        {
            int v=G[u][i];
            if(sccno[u]!=sccno[v])
                in0[sccno[v]]=out0[sccno[u]]=false;
        }
        int a=0, b=0;
        for(int i=1;i<=scc_cnt;i++)
        {
            if(in0[i]) a++;
            if(out0[i]) b++;
        }
        if(scc_cnt==1) printf("1\n0\n");
        else printf("%d\n%d\n",a,max(a,b));
    }
    return 0;
}
缩点

题意:

        给你一个有向图,现在问你图中有多少个顶点满足下面要求:任何其他的点都有路可以走到该顶点. 输出满足要求顶点的数目.

分析:

        首先我们把图的各个强连通分量算出来,对于分量A,如果A中的点a是那个图中所有点都可以到达的点,那么A中的其他所有点也都符合要求.

        所以我们只需要把每个分量缩成一点,得到一个DAG有向无环图.然后看该DAG中的哪个点是所有其他点都可以到达的即可.那么该点代表的分量中的节点数就是所求答案.

        如果DAG中出度为0的点仅有一个,那个出度为0的点代表的分量就是我们所找的分量.否则输出0.(这个结论需要自己仔细验证体会)

        可不可能DAG中有两个点(分量)是满足要求的?(即分量中的所有点都是其他点可到达的)不可能,因为这两个分量如果互相可达,就会合并成一个分量.

        会不会出现就算出度为0的点只有一个,但是DAG中的其他点到不了该出度为0的点,那么也应该输出0呢?如果DAG其他的点到不了出度为0的点,那么其他点必然还存在一个出度为0的点.矛盾.

AC代码:


#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<stack>
using namespace std;
const int maxn=10000+10;
int n,m;
vector<int> G[maxn];
stack<int> S;
int dfs_clock,scc_cnt;
int pre[maxn],sccno[maxn],low[maxn];
int num[maxn];//num[i]=x表第i个分量中有x个节点
bool out0[maxn];//标记新DAG图出度为0的节点
void dfs(int u)
{
    pre[u]=low[u]=++dfs_clock;
    S.push(u);
    for(int i=0;i<G[u].size();i++)
    {
        int v=G[u][i];
        if(!pre[v])
        {
            dfs(v);
            low[u]=min(low[u],low[v]);
        }
        else if(!sccno[v])
            low[u]=min(low[u],pre[v]);
    }
    if(low[u]==pre[u])
    {
        scc_cnt++;
        while(true)
        {
            int x=S.top(); S.pop();
            sccno[x]=scc_cnt;
            num[scc_cnt]++;
            if(x==u) break;
        }
    }
}
void find_scc(int n)
{
    scc_cnt=dfs_clock=0;
    memset(pre,0,sizeof(pre));
    memset(sccno,0,sizeof(sccno));
    memset(num,0,sizeof(num));
    for(int i=1;i<=n;i++)
        if(!pre[i]) dfs(i);
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) G[i].clear();
    while(m--)
    {
        int u,v;
        scanf("%d%d",&u,&v);
        G[u].push_back(v);
    }
    find_scc(n);
    for(int i=1;i<=scc_cnt;i++) out0[i]=true;
    for(int u=1;u<=n;u++)
    for(int i=0;i<G[u].size();i++)
    {
        int v=G[u][i];
        int x=sccno[u], y=sccno[v];
        if(x!=y) out0[x]=false;
    }
    int a=0,pos;
    for(int i=1;i<=scc_cnt;i++)
        if(out0[i]) a++,pos=i;
    if(a==1) printf("%d\n",num[pos]);
    else printf("0\n");
    return 0;
}
判断是否是强连通图

题意:

        给你一个有向图,问你该图是否是一个强连通的图?

分析:

        直接tarjan强连通算法求出scc_cnt(强连通分量的数目),看看scc_cnt是否为1即可.

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<stack>
using namespace std;
const int maxn=10000+10;
int n,m;
vector<int> G[maxn];
stack<int> S;
int dfs_clock, scc_cnt;
int pre[maxn],low[maxn],sccno[maxn];
void dfs(int u)
{
    pre[u]=low[u]=++dfs_clock;
    S.push(u);
    for(int i=0;i<G[u].size();i++)
    {
        int v=G[u][i];
        if(!pre[v])
        {
            dfs(v);
            low[u]=min(low[u],low[v]);
        }
        else if(!sccno[v])
            low[u]=min(low[u],pre[v]);
    }
    if(low[u]==pre[u])
    {
        scc_cnt++;
        while(true)
        {
            int x=S.top(); S.pop();
            sccno[x]=scc_cnt;
            if(x==u) break;
        }
    }
}
void find_scc(int n)
{
    dfs_clock=scc_cnt=0;
    memset(pre,0,sizeof(pre));
    memset(sccno,0,sizeof(sccno));
    for(int i=1;i<=n;i++)
        if(!pre[i]) dfs(i);
}
int main()
{
    while(scanf("%d%d",&n,&m)==2)
    {
        if(n==0 && m==0) break;
        for(int i=1;i<=n;i++) G[i].clear();
        while(m--)
        {
            int u,v;
            scanf("%d%d",&u,&v);
            G[u].push_back(v);
        }
        find_scc(n);
        printf("%s\n",scc_cnt==1?"Yes":"No");
    }
    return 0;
}
求桥

题意:

       现在有个(可重边)无向图,无向图的每条边上都有一定数目的守卫,你现在想派人去炸掉这个图的一条边,是的该图不连通。但是你只能炸1条边且如果该边守卫为x人,那么你至少要派x个人过去。所以现在问你最少需要派多少人出发?

分析:

       本题的本质还是无向图求桥,且求得是守卫数目最少的那个桥。但是本题有3个点要注意:

       1.所给的图可能不连通,且不连通的时候不需要炸,输出0.

       2.当所要去炸的桥上的守卫数=0时,我们需要派的人数是1不是0.

       3.任意两个节点u与v之间可能存在多条边。

       对于上面的1与2点,我们在原始tarjan()函数运行完后加一些判断就能解决.

       不过对于重边无向图,首先我们要用邻接表来保存图了(不能再用vector的邻接矩阵了).

       然后之前无重边的时候我们都是用过fa来标记父节点的,如果u的儿子等于fa,那么直接跳过。即如果u不通过儿子连回fa的话,low[u]==pre[u]肯定>pre[fa]。现在本题其实u是可以通过另一条(fa,u)的边连回fa的,所以这里即使u不通过儿子连回fa的话,low[u]==也可以==pre[fa]。因为fa通过边1到u,u可以通过边2到fa。

       所以本题把无向图转换成有向图来做:

       把每条无向边分为两条有向边i与i+1,如果u通过边i到达了v,那么v中必然有一条边是i^1且可以通过该i^1边到u.所以如果在v节点遍历时到达i^1边时,我们直接跳过.

       具体实现还是需要体会代码才能清晰.


#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=1000+10;
const int maxm=2*1000*1000+100;
int n,m;
int tot;
int head[maxn];
struct Edge
{
    int to,next,w;
}edges[maxm];
void add_edge(int u,int v,int w)
{
    edges[tot]=(Edge){v,head[u],w};
    head[u]=tot++;
    edges[tot]=(Edge){u,head[v],w};
    head[v]=tot++;
}
 
int pre[maxn],low[maxn];
int dfs_clock,point_num;
int ans;
void tarjan(int u,int E)
{
    low[u]=pre[u]=++dfs_clock;
    for(int e=head[u];e!=-1;e=edges[e].next)
    {
        int v=edges[e].to;
        if(e==(E^1)) continue;
        if(!pre[v])
        {
            tarjan(v,e);
            low[u]=min(low[u],low[v]);
            if(low[v]>pre[u])
                ans=min(ans,edges[e].w);
        }
        else low[u]=min(low[u],pre[v]);
    }
    point_num++;
}
int main()
{
    while(scanf("%d%d",&n,&m)==2&&n)
    {
        ans=1000000;
        dfs_clock=point_num=tot=0;
        memset(pre,0,sizeof(pre));
        memset(head,-1,sizeof(head));
        for(int i=0;i<m;i++)
        {
            int u,v,w;
            scanf("%d%d%d",&u,&v,&w);
            add_edge(u,v,w);
        }
        tarjan(1,-1);
        if(point_num<n) printf("0\n");          //图不连通,不用炸
        else if(ans==1000000) printf("-1\n");   //图中无桥
        else if(ans==0) printf("%d\n",1);       //桥上兵为0
        else printf("%d\n",ans);
    }
    return 0;
}
题意:

        给你一个无向图(不一定连通),现在问你从该图中删除任意一个顶点之后,该无向图所具有的连通分量数目最大是多少?

分析:

        本题与前一题不一样,前一题是要你判定哪些点是割点。但是这题需要你求出割点到底能割出几个连通分量。本题的无向图可能不连通。我们只需要考虑在同一个连通分量时,一个割点到底能把图割成几部分即可。

        在dfs的时候,我们用cut[i]==X表示在dfs树中当i节点被删除时,i节点的X个儿子被切割开来(可以认为cut[i]是i节点与它的儿子连接的桥边的数目)。注意:如果i是根且其儿子只有1个,虽然i不是割点,cut[i]依然=1。如果i节点非割点,那么cut[i]=0。如果i是割点,那么cut[i]就是i被删除后将割出来的儿子数目。

       然后我们求出了每个点的cut[i]值,即i点被删除,会有cut[i]个儿子树被割出来。如果i是dfs树的非根节点,那么cut[i]== 切除i之后增加的连通分量数目。如果i是dfs树的根节点,那么cut[i]-1才是切除i之后增加的连通分量数目(想想是不是)。

        如果原始cut[i]=0,表示i是孤立的一点,此时cut[i]-1=-1.

        如果原始cut[i]=1,表示i为根且有一个儿子,此时cut[i]-1=0.

        如果原始cut[i]>=2,表示i为根且分割了>=2个儿子,此时cut[i]-1>=1.

        (以上含义需仔细体会验证)

AC代码:


#include<cstdio>
#include<cstring>
#include<vector>
#include<algorithm>
using namespace std;
const int maxn=10000+10;
int n,m;
vector<int> G[maxn];
int cut[maxn];
int low[maxn];
int pre[maxn];
int dfs_clock;
int tarjan(int u,int fa)
{
    int lowu= pre[u]=++dfs_clock;
    for(int i=0;i<G[u].size();i++)
    {
        int v=G[u][i];
        if(!pre[v])
        {
            int lowv=tarjan(v,u);
            lowu=min(lowv,lowu);
            if(lowv>=pre[u]) cut[u]++;
        }
        else if(pre[v]<pre[u] && v!=fa)
            lowu = min(lowu,pre[v]);
    }
    return low[u]=lowu;
}
int main()
{
    while(scanf("%d%d",&n,&m)==2&&n)
    {
        dfs_clock=0;
        memset(cut,0,sizeof(cut));
        memset(pre,0,sizeof(pre));
        for(int i=0;i<n;i++) G[i].clear();
        for(int i=0;i<m;i++)
        {
            int u,v;
            scanf("%d%d",&u,&v);
            G[u].push_back(v);
            G[v].push_back(u);
        }
        int sum=0;//计数连通分量数目
        int max_cut=-10000;
        for(int i=0;i<n;i++)if(!pre[i])
        {
            sum++;
            tarjan(i,-1);
            cut[i]--;
        }
        for(int i=0;i<n;i++)
            max_cut=max(max_cut,cut[i]);
        printf("%d\n",sum+max_cut);
    }
    return 0;
}
割点

题意:

        给你一个无向图,问你这个图中有多少个割点.不过该题的输入格式说的比较难懂.这里解释一下:每个实例第一行是N,表示节点数.接下来可能有最多N行描述边信息的.

        其中这N行每行都是这样的:每行第一个数表示该行的主顶点u,接着的所有数字表示副顶点v1,v2,v3…等.表示u与v1,u与v2,u与v3分别都有一条边.

        最后这个实例以一个0表示结束.

        当然所有实例之后还有一个0(N=0),表示输入结束.

分析:

        直接求割点,用刘汝佳书上的tarjan算法模板即可AC,注意不要把模板打错.

#include<cstdio>
#include<cstring>
#include<vector>
using namespace std;
const int maxn = 100+10;
int n;
int dfs_clock;
vector<int> G[maxn];
int pre[maxn],low[maxn];
bool iscut[maxn];
int dfs(int u,int fa)
{
    int lowu=pre[u]=++dfs_clock;
    int child=0;
    for(int i=0;i<G[u].size();i++)
    {
        int v=G[u][i];
        if(!pre[v])
        {
            child++;
            int lowv=dfs(v,u);
            lowu=min(lowv,lowu);
            if(lowv >= pre[u]) //错误1,这里写成了lowu>=pre[u]了
                iscut[u]= true;
        }
        else if(pre[v]<pre[u] && v!=fa)
            lowu=min(lowu,pre[v]);
    }
    if(fa<0 && (child==1) ) iscut[u]=false;
    return low[u]=lowu;
}
int get_v(char *s)
{
    int v=0;
    for(int i=0;s[i];i++)
        v=v*10+s[i]-'0';
    return v;
}
int main()
{
    while(scanf("%d",&n)==1&&n)
    {
        dfs_clock=0;
        memset(pre,0,sizeof(pre));
        memset(iscut,0,sizeof(iscut));
        for(int i=1;i<=n;i++) G[i].clear();
        char str[10];
        while(scanf("%s",str)==1)
        {
            if(str[0]=='0') break;
            int u=get_v(str);
            while(scanf("%s",str)==1)
            {
                int v=get_v(str);
                G[u].push_back(v);
                G[v].push_back(u);
                if(getchar()=='\n') break;
            }
        }
        dfs(1,-1);
        int ans=0;
        for(int i=1;i<=n;i++)if(iscut[i])
            ans++;
        printf("%d\n",ans);
    }
    return 0;
}
MAP映射+求桥

题意:

      给你一个无向图(可能不连通,但是无自环,无重边),如果本图不连通,那么直接输出0。否则要你输出图中的每条桥边,要求按输入边的顺序输出。

分析:

       由于图可能不连通,所以我们只用tarjan(1,-1)即可。然后判断是否还有节点的pre值==0。如果存在这种点,那么该图不连通.

       输出每条桥不难,直接用刘汝佳训练指南的模板即可。但是本题要求按边的输入顺序输出桥,所以我们不能在dfs的过程中输出了。我们必须事后按顺序判断每条边是否是桥。

       如何在dfs事后,判断一条边是不是桥呢?

       对于边(u,v)来说,只要low[u]>pre[v] 或 low[v]>pre[u]那么(u,v)边一定是桥。

       (仔细想想这个结论)

       代码中我用map来实现一个字符串到点编号的映射,那么一个输入边就是由两个字符串节点node组成的了.我们先求完所有节点的low值.然后在退出tarjan()函数后,重新扫描每一条边的两个节点u和v的low和pre值,即可判断该边是否是桥。
割点

题意:

        给你一个无向图,问你该图中有多少割点.且每个割点能把该图分为几个连通分量

分析:

        本题与POJ 2117很类似,也是用cut[i]数组来计数i节点所能割的儿子数.(不过注意:对于根节点,如果它不是割点,那么cut[i]==0而不是-1了),具体分析完全可以参考POJ 2117:

http://blog.youkuaiyun.com/u013480600/article/details/30976823

        注意:该题中只有真正的割点其cut值才非0,如果i点不是割点.那么cut[i]==0.(不会存在-1的情况)

对于非根节点的割点,它能分割图为cut[i]+1个连通分量,对于根节点割点,它能分割图为cut[i]个连通分量

AC代码:
--------------------- 
作者:focus_best 
来源:优快云 
原文:https://blog.youkuaiyun.com/u013480600/article/details/30980387 
版权声明:本文为博主原创文章,转载请附上博文链接!

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
const int maxn= 1000+10;
vector<int> G[maxn], ans;
int pre[maxn],cut[maxn],low[maxn];
int dfs_clock;
int tarjan(int u,int fa)
{
    int lowu=pre[u]=++dfs_clock;
    int child=0;
    for(int i=0;i<G[u].size();i++)
    {
        int v=G[u][i];
        if(!pre[v])
        {
            child++;
            int lowv=tarjan(v,u);
            lowu=min(lowv,lowu);
            if(lowv>=pre[u])
                cut[u]++;
        }
        else if(pre[v]<pre[u] && v!=fa)
            lowu=min(lowu,pre[v]);
    }
    if(fa<0)//根
    {
        if(child>=2) ans.push_back(u);
    }
    else if(cut[u]>=1) ans.push_back(u),cut[u]++; //非根割点,所分连通分量还要+1
    return low[u]=lowu;
}
int main()
{
    int u,v;
    int kase=1;
    while(scanf("%d",&u)==1&&u)
    {
        dfs_clock=0;
        memset(pre,0,sizeof(pre));
        memset(cut,0,sizeof(cut));
        ans.clear();
        for(int i=1;i<=1000;i++) G[i].clear();
        while(true)
        {
            scanf("%d",&v);
            G[u].push_back(v);
            G[v].push_back(u);
            scanf("%d",&u);
            if(u==0) break;
        }
        for(int i=1;i<=1000;i++)if(pre[i]==0 && G[i].size()>0)
        {
            tarjan(i,-1);
        }
        sort(&ans[0],&ans[0]+ans.size());
        if(ans.size()>0)
        {
            printf("Network #%d\n",kase++);
            for(int i=0;i<ans.size();i++)
                printf("  SPF node %d leaves %d subnets\n",ans[i],cut[ans[i]]);
 
        }
        else printf("Network #%d\n  No SPF nodes\n",kase++);
        puts("");           //别忘了这个回车
    }
    return 0;
}
欧拉回路

题意:

        给你多个木棍,每个木棍两段涂上颜色,两根木棍只有在相同颜色的一端才能连接,问你能不能使所有木棍都连接成一条直线路.

分析:

        将输入的每个颜色看出是图的一个点,然后每条木棍正好是连接了两种颜色的一条边,我们只需要判断这个图中是否存在欧拉道路或欧拉回路即可.(考虑一下这种情况,如果一根棍子首尾是同种颜色怎么办?其实这个可以不用考虑,这就是一个自环,如果不含自环的图有欧拉回路,那么含自环的图一定也有欧拉回路)

        处理流程:依次读入每根木棍的两段的颜色A和B,然后尝试将A与B插入字典树,如果不存在A颜色,就新加入A颜色,并且给A颜色一个数字编号i,B颜色的编号是j.

        i和j就是图中的两个节点且它们是连通的且它们的度数还要都+1.合并i与j的并查集(如果i与j是同种颜色,那么就不会合并它们).当处理完所有的木棍后,看看该图是不是连通的.在看看该图的所有节点的度数是不是满足下面要求:

所有点的度数都是偶数 或 只有2个点的度数是奇数.

AC代码:1A,438ms


--------------------- 
作者:focus_best 
来源:优快云 
原文:https://blog.youkuaiyun.com/u013480600/article/details/23200893 
版权声明:本文为博主原创文章,转载请附上博文链接!
1 定义

欧拉通路(Euler tour)——通过图中每条边一次且仅一次,并且过每一顶点的通路。

欧拉回路 (Eulercircuit)——通过图中每条边一次且仅一次,并且过每一顶点的回路。

欧拉图——存在欧拉回路的图。

2 无向图是否具有欧拉通路或回路的判定

G有欧拉通路的充分必要条件为:G 连通,G中只有两个奇度顶点(它们分别是欧拉通路的两个端点)。

G有欧拉回路(G为欧拉图):G连通,G中均为偶度顶点。

3 有向图是否具有欧拉通路或回路的判定

D有欧拉通路:D连通,除两个顶点外,其余顶点的入度均等于出度,这两个特殊的顶点中,一个顶点的入度比出度大1,另一个顶点的入度比出度小1。

D有欧拉回路(D为欧拉图):D连通,D中所有顶点的入度等于出度。

 (注意:这里说有向图连通,说的是有向图是弱连通图。即把有向图中的边变成无向边,只要该图连通,那么原有向图即是弱连通图。实际中可用并查集判断是否弱连通)

4 混合图。混合图也就是无向图与有向图的混合,即图中的边既有有向边也有无向边

欧拉回路输出点轨迹

意:

        现在有N个农场和M条路,要求你走过这M条路每条2次且这两次是反向的.要你输出从1号农场走到完所有M条路两次之后回到1号农场的点的轨迹.保证这种轨迹存在.其中点(农场)从1开始编号到N.

分析:

        本题其实就是要求输出一个欧拉回路的点轨迹.其中每条边分为两个方向的有向边分别标记即可.因为本题边数可能有50000条以上,所以用的邻接表保存边,不可以在从0-50000遍历所有边了,那样肯定超时.

AC代码:
--------------------- 
作者:focus_best 
来源:优快云 
原文:https://blog.youkuaiyun.com/u013480600/article/details/30211605 
版权声明:本文为博主原创文章,转载请附上博文链接!

#include<cstdio>
#include<cstring>
using namespace std;
const int maxm=50000+10;
const int maxn=10000+10;
int n,m;
struct Edge
{
    int to;
    int next;
    bool vis;
}edges[maxm*2];   //错误1,这里记得要*2
int cnt;//边数
int head[maxn];//头结点数
void add(int u,int v)
{
    edges[cnt].to=v;
    edges[cnt].next=head[u];
    edges[cnt].vis=false;
    head[u]=cnt++;
}
void euler(int u)
{
    for(int e=head[u];e!=-1;e=edges[e].next)if(!edges[e].vis)
    {
        edges[e].vis=true;
        euler(edges[e].to);
    }
    printf("%d\n",u);
}
int main()
{
    cnt=0;
    memset(head,-1,sizeof(head));
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++)
    {
        int u,v;
        scanf("%d%d",&u,&v);
        add(u,v);
        add(v,u);
    }
    euler(1);
    return 0;
}
欧拉通路

题意:

        给你多个单词,问你能不能将所有单词组成这样一个序列:序列的前一个单词的尾字母与后一个单词的头字母相同.

分析:

        把每个单词看成一条有向边,把26个字母看成是图的节点.这就是一个问你有向图是否存在欧拉通路/回路的问题.

        有向图存在欧拉路必须满足两个条件:

        1.    有向图弱连通

        2.    图中所有点入度==出度 或只有两个点的入度!=出度且这两个中一个入度-出度=1,另一个出度-入度=1.

        注意:这里每个单词必须看成有向边,而不能看成无向边.因为对于每个单词来说只能从单词头走到单词尾.

AC代码:
--------------------- 
作者:focus_best 
来源:优快云 
原文:https://blog.youkuaiyun.com/u013480600/article/details/30214865 
版权声明:本文为博主原创文章,转载请附上博文链接!

#include<cstdio>
#include<cstring>
using namespace std;
const int maxn=26+5;
int fa[maxn];
int in[maxn],out[maxn];
int m;//单词数
int findset(int u)
{
    if(fa[u]==-1) return u;
    return fa[u]=findset(fa[u]);
}
int main()
{
    int T; scanf("%d",&T);
    while(T--)
    {
        memset(in,0,sizeof(in));
        memset(out,0,sizeof(out));
        memset(fa,-1,sizeof(fa));
        scanf("%d",&m);
        for(int i=0;i<m;i++)
        {
            char str[1200];
            scanf("%s",str);
            int len=strlen(str);
            int u=str[0]-'a', v=str[len-1]-'a';
            in[u]++;
            out[v]++;
            u=findset(u), v=findset(v);
            if(u!=v) fa[u]=v;
        }
        int cnt=0;
        for(int i=0;i<26;i++)
            if( (in[i]||out[i]) && findset(i)==i ) cnt++;
        if(cnt>1)
        {
            printf("The door cannot be opened.\n");
            continue;
        }
        int c1=0, c2=0, c3=0;//分别表示入度!=出度时的三种情况
        for(int i=0;i<26;i++)
        {
            if(in[i]==out[i]) continue;
            else if(in[i]-out[i]==1) c1++;
            else if(out[i]-in[i]==1) c2++;
            else c3++;
        }
        if( ( (c1==c2&&c1==1)||(c1==c2&&c1==0) )&&c3==0 )
            printf("Ordering is possible.\n");
        else
            printf("The door cannot be opened.\n");
    }
    return 0;
}
判断

无向图G存在欧拉通路的充分必要条件:G为连通图,并且G仅有两个奇度结点(度数为奇数的节点)或者无奇度结点。
推论1:当无向图G是有两个奇度的连通图时,G的欧拉通路必定以这两个结点为端点。
推论2:当无向图G是无奇度的连通图时,G必有欧拉回路。
推论3:无向图G存在欧拉回路的充分必要条件:G为无奇度结点的连通图,并且G仅有两个奇度结点(度数为奇数的节点)或者无奇度结点。

有向图D存在有向欧拉通路的充分必要条件:D为有向图,D的基图连通,并且所有顶点的出度与入度都相等(情况1);或者除了两个定点外,其余顶点的出度与入度都相等,而这两个顶点中,一个顶点的出度与入度之差为1,另一个出度与入度只差为-1(情况2)。
推论(1):情况1说明存在的是有向欧拉回路。
推论(2):情况2说明存在的是有向欧拉通路,通路以出度与入度之差为1的顶点作为起点,以出度与入度之差为-1的顶点作为终点。
推论(3):有向图D存在有向欧拉回路的充分必要条件:D的基图为连通图,并且所有顶点的出入与入度都相等。
判断

通过图(无向图或有向图)中所有边一次且仅一次行遍图中所有顶点的通路称为欧拉通路,通过图中所有边一次且仅一次行遍所有顶点的回路称为欧拉回路。具有欧拉回路的图称为欧拉图(Euler Graph),具有欧拉通路而无欧拉回路的图称为半欧拉图。

        1 定义

        欧拉通路(Euler tour)——通过图中每条边一次且仅一次,并且过每一顶点的通路。

        欧拉回路 (Eulercircuit)——通过图中每条边一次且仅一次,并且过每一顶点的回路。

        2 无向图是否具有欧拉通路或回路的判定

        G有欧拉通路的充分必要条件为:G 连通,G中只有两个奇度顶点(它们分别是欧拉通路的两个端点)。

        G有欧拉回路(G为欧拉图):G连通,G中均为偶度顶点。

        3 有向图是否具有欧拉通路或回路的判定

        D有欧拉通路:D连通,除两个顶点外,其余顶点的入度均等于出度,这两个特殊的顶点中,一个顶点的入度比出度大1,另一个顶点的入度比出度小1。

        D有欧拉回路(D为欧拉图):D连通,D中所有顶点的入度等于出度。

        (注意:这里说有向图连通,说的是有向图是弱连通图。即把有向图中的边变成无向边,只要该图连通,那么原有向图即是弱连通图。实际中可用并查集判断是否弱连通)

         注意下面打印节点的代码,其实打印欧拉路径的节点轨迹可以先打印每条欧拉路上的边,然后取每条边的头结点即可(最后一条边还需要取尾部结点)。

         对于一般的单词首尾相连的问题,一般都是转化为有向图的欧拉通路问题(而不是无向图),比如”给你多个单词,问你能不能将所有单词组成这样一个序列:序列的前一个单词的尾字母与后一个单词的头字母相同”,如果你把每个单词看出无向的边,那么最终求出的欧拉通路可能存在两个单词尾部和尾部相连的情况。
--------------------- 
作者:focus_best 
来源:优快云 
原文:https://blog.youkuaiyun.com/u013480600/article/details/44805491 
版权声明:本文为博主原创文章,转载请附上博文链接!
欧拉回路-通路打印

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=100+5;
 
//无向图打印欧拉路径或回路
//输入保证是一个n顶点,m条边的具有欧拉回路或欧拉路径的无向图
 
int n;//图中顶点,顶点编号1到n
int m;//图中边
int G[maxn][maxn];
int vis[maxn][maxn];//vis[i][j]==1表示i与j点直接存在一条边
 
//打印欧拉路径或欧拉回路(必须本图有欧拉路径或回路才行)
//打印的结果中最后一条边才是u开始的,打印的第一条边不一定是u开始的
//如果是打印欧拉路径,那么输入的u一定要是起点之一,即度数为奇数的点之一
//否则euler打印的的边不会构成欧拉路径,只不过是乱序打印图中所有的边而已
void euler(int u)
{
    for(int v=1;v<=n;v++)if(vis[u][v]||vis[v][u])
    {
        //递归思想,去掉了u与v这条边,
        //余图还是一个具有欧拉道路的图,且v变成一个起点了
        vis[u][v]=vis[v][u]=0;
        euler(v);
        printf("%d %d\n",u,v);
    }
}
 
//输出欧拉回路或路径上按顺序经过的节点
//u也必须要是起点之一,否则输出的也是乱序点而已
void euler_point(int u)
{
    for(int v=1;v<=n;v++)if(vis[u][v] || vis[v][u])
    {
        vis[u][v]=vis[v][u]=0;
        euler_point(v);
    }
    printf("%d\n",u);
}
 
int main()
{
    while(scanf("%d%d",&n,&m)==2)
    {
        memset(G,0,sizeof(G));
        memset(vis,0,sizeof(vis));
        for(int i=1;i<=m;i++)
        {
            int u,v;
            scanf("%d%d",&u,&v);
            G[u][v]=G[v][u]=1;//无向图
            vis[u][v]=vis[v][u]=1;
        }
 
        int u;
        scanf("%d",&u);
        euler_point(u);
 
    }
    return 0;
}
拓扑排序

判断

#include<cstdio>
#include<cstring>
#include<vector>
#include<queue>
using namespace std;
const int maxn=100+10;
int n,m;
vector<int> G[maxn];
int in[maxn];
bool topo()
{
    queue<int> Q;
    int sum=0;
    for(int i=0;i<n;i++)if(in[i]==0)
        Q.push(i);
    while(!Q.empty())
    {
        int u=Q.front(); Q.pop();
        sum++;
        for(int i=0;i<G[u].size();i++)
        {
            int v=G[u][i];
            if(--in[v]==0) Q.push(v);
        }
    }
    return sum==n;
}
int main()
{
    while(scanf("%d%d",&n,&m)==2&&n)
    {
        memset(in,0,sizeof(in));
        for(int i=0;i<n;i++) G[i].clear();
        for(int i=0;i<m;i++)
        {
            int u,v;
            scanf("%d%d",&u,&v);
            G[u].push_back(v);
            in[v]++;
        }
        printf("%s\n",topo()?"YES":"NO");
    }
    return 0;
}
优先队列+拓扑

题意:

        给你N个点M条有向边,要求你输出字典序最小的拓扑排序序列.

分析:

        前面已经做过很多这种题了,没什么说的直接输出字典序最小的拓扑序列即可.

        注意:非递归求字典序最小的拓扑序列需要用到优先队列,且要是小值优先的队列.大致的思想就是队列Q总是将当前在入度为0的最小节点优先取出.保证了字典序最小.

AC代码:

#include<cstdio>
#include<cstring>
#include<vector>
#include<queue>
using namespace std;
const int maxn=500+10;
int n,m;
vector<int> G[maxn];
int in[maxn];
void topo()
{
    priority_queue<int,vector<int>,greater<int> > Q; //保证小值int先出队列
    int ans[maxn],cnt=0;
    for(int i=1;i<=n;i++)if(in[i]==0) Q.push(i);
    while(!Q.empty())
    {
        int u=Q.top(); Q.pop();
        ans[cnt++]=u;
        for(int i=0;i<G[u].size();i++)
        {
            int v=G[u][i];
            if(--in[v]==0)
                Q.push(v);
        }
    }
    printf("%d",ans[0]);
    for(int i=1;i<n;i++)
        printf(" %d",ans[i]);
    puts("");
}
int main()
{
    while(scanf("%d%d",&n,&m)==2)
    {
        memset(in,0,sizeof(in));
        for(int i=1;i<=n;i++) G[i].clear();
        for(int i=1;i<=m;i++)
        {
            int u,v;
            scanf("%d%d",&u,&v);
            G[u].push_back(v);
            in[v]++;
        }
        topo();
    }
    return 0;
}


拓扑序列

题意:

        大意就是给你一个N个点的图,并且给你图中的有向边,要你输出一个可行的点拓扑序列即可.输入格式为,第一行点数N,以下接着有N行,每行以0结尾.第i行包含了以i点为起点的有向边所指的所有节点.

分析:

        直接用 vector G[maxn][maxn] 和in[maxn] 数组来建图和表示入度即可.

        然后用队列的方式消除图的所有边,求拓扑序列即可.
--------------------- 
作者:focus_best 
来源:优快云 
原文:https://blog.youkuaiyun.com/u013480600/article/details/30516301 
版权声明:本文为博主原创文章,转载请附上博文链接!

#include<cstdio>
#include<cstring>
#include<vector>
#include<queue>
using namespace std;
const int maxn=100+10;
int n;
vector<int> G[maxn];
int in[maxn];
int ans[maxn];
void topo()
{
    queue<int> Q;
    for(int i=1;i<=n;i++)if(in[i]==0)
        Q.push(i);
    int cnt=0;
    while(!Q.empty())
    {
        int u=Q.front(); Q.pop();
        ans[cnt++]=u;
        for(int i=0;i<G[u].size();i++)
        {
            int v=G[u][i];
            if(--in[v]==0) Q.push(v);
        }
    }
}
int main()
{
    while(scanf("%d",&n)==1&&n)
    {
        for(int i=1;i<=n;i++)
        {
            G[i].clear();//初始化
            in[i]=0;     //初始化
            int v;
            while(1)
            {
                scanf("%d",&v);
                if(v==0) break;
                G[i].push_back(v);
                in[v]++;
            }
        }
        topo();
        printf("%d",ans[0]);
        for(int i=1;i<n;i++)
            printf(" %d",ans[i]);
        printf("\n");
    }
    return 0;
}
输出所有拓扑序列

题意:

        输入数据有两行,第一行给你由26个单个小写字母表示的变量,第二行给出成对的变量如(x,y),表示x要在y前面.然后要你输出所有可能的变量序列且满足第二行的顺序约束.如果存在多解,按字典序从小到大输出所有结果.

分析:

        注意了由于这里要输出所有的合法拓扑排序序列且要求字典序从小到大输出.并且此题保证了拓扑排序一定存在,所以我们就不能用刘汝佳的那个拓扑排序方法了.

        这里我们必须用DFS+回溯构造法来按字典序从小到大构造所有可能的拓扑排序.

        首先本题的本质是用题目所给的字母构造一个符合要求的字母序列.这个字母需要要满足什么要求呢?

        1.当前被选的字母必须有效(即mark[i]==true)且当前被选的字母vis=false(即还没被选)。

        2.当我们从前到后依次选择一个字母x放进topo数组的时候,我们要保证在topo数组的当前位置cnt的前面那些位置中不会出现y这种字母。其中y<x,即y被要求出现在x后面。

        3.可能有人会有疑问,就算保证了x出现在它的所有后继前面,但是你没有保证z(z>x)出现在x前面啊,那如果已经选了x的时候还没选z,怎么能形成合法的序列呢?

        解答:当选了x时还没选z的话,在之后的递归中,标记当前位置的cnt就不可能==n,所以dfs不会产生一个可行解,这个dfs就无疾而终了。也就是说程序会自动忽略这种非法解。

AC代码:
#include<cstdio>
#include<cstring>
using namespace std;
const int maxn=30;
const int maxm=500;
int n;//有效字母数
int G[maxn][maxn];
int vis[maxn];
int ans[maxn];
int cnt;
bool mark[maxn];//标记当前字母出现在变量中
bool ok(int i,int cnt)//如果在ans[0,cnt-1]出现了一个本应在i后面才出现的字母,那么返回false
{
    for(int j=0;j<cnt;j++)
        if(G[i][ans[j]]) return false;
    return true;
}
void dfs(int cnt)
{
    if(cnt==n)
    {
        for(int i=0;i<n;i++)
            printf("%c",ans[i]+'a');
        printf("\n");
    }
    else for(int i=0;i<26;i++)if(mark[i]&&!vis[i]&&ok(i,cnt))
    {
        vis[i]=1;
        ans[cnt]=i;
        dfs(cnt+1);
        vis[i]=0;
    }
}
int main()
{
    char str[1000];
    while(gets(str))
    {
        n=0;
        memset(mark,0,sizeof(mark));
        memset(G,0,sizeof(G));
        memset(vis,0,sizeof(vis));
        for(int i=0;str[i];i++)if(str[i]!=' ')
            mark[str[i]-'a']=true, n++;
        gets(str);
        for(int i=0;str[i];i++)if(str[i]!=' ')
        {
            int a,b;
            a=str[i++]-'a';
            while(str[i]==' ')
                i++;
            b=str[i]-'a';
            G[a][b]=1;
        }
        dfs(0);//表示当前正在构造第0个位置
        puts("");
    }
    return 0;
}
拓扑判断

题意:

        给你一个特殊的有向图,该有向图的任意两个节点u与v之间有且仅有一条单向边,现在问你该有向图是否存在由3个节点构成的环。

分析:

       该图本质是拓扑排序题.如果该图可以拓扑排序,那么不存在3节点的环,否则存在3节点的环.下面是证明:

       首先存在3节点的环-> 不能拓扑排序.

       其次我们现在证明 不能拓扑排序->存在3节点的环.

假设图不能拓扑排序,那么该图一定存在一个n节点(n>=3)的环.假设该环为a->b->c->d->….. 等. 由于a与c之间必然存在边,所以如果此边为c->a,那么就存在3节点环了.否则此边为a->c的话,那么a->c->d->…构成了一个n-1节点的环.依次类推,我们可以有n节点的环得出该图必然存在n-1,n-2,…一直到3节点的环.

       所以能拓扑排序 <==>不存在3节点环.

AC代码: (读输入的时候,如果一次读一个字符会超时)

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
const int maxn=2000+10;
int n;
vector<int> G[maxn];
int in[maxn];
bool topo()
{
    queue<int> Q;
    for(int i=0;i<n;i++)
        if(!in[i]) Q.push(i);
    int sum=0;
    while(!Q.empty())
    {
        int u=Q.front(); Q.pop();
        sum++;
        for(int i=0;i<G[u].size();i++)
        {
            int v=G[u][i];
            if(--in[v]==0) Q.push(v);
        }
    }
    return sum==n;
}
int main()
{
    int T; scanf("%d",&T);
    for(int kase=1;kase<=T;kase++)
    {
        scanf("%d",&n);
        memset(in,0,sizeof(in));
        for(int i=0;i<n;i++)
        {
            G[i].clear();
            char str[maxn];
            scanf("%s",str);
            for(int j=0;j<n;j++)if(str[j]=='1')
            {
                G[i].push_back(j);
                in[j]++;
            }
        }
        printf("Case #%d: %s\n",kase,topo()?"No":"Yes");
    }
    return 0;
}

点-边双联通分量

点-双连通图:一个连通的无向图内部没有割点,那么该图是点-双连通图。

        注意:孤立点,以及两点一边这两种图都是点-双连通的。因为它们都是内部无割点。

        边-双连通图:一个连通的无向图内部没有桥,那么该图就是边-双连通的。

        注意:孤立点是边-双连通的,但是两点一边不是边-双连通的。

        由上面定义可以知道:点-双连通图不一定是边-双连通的。

        对于一张无向图,点-双连通的极大子图称为双连通分量。不难发现,每条边恰好属于一个双连通分量(所以两点一边是一个点-双连通分量)。但不同双连通分量可能会有公共点,可以证明不同双连通分量最多只有一个公共点,且它一定是割顶。另一方面任意割顶都是至少两个不同的点-双连通分量的公共点。

        边-双连通的极大子图称为边-双连通分量。除了桥不属于任何边-双连通分量外,其他每条边恰好属于一个边-双连通分量,而且把所有桥删除之后,每个连通分量对应原图中的一个边-双连通分量。

        总之:

        判断一个图是不是点-双连通的只要看图中是否有割点。

        判断一个图是不是边-双连通的只要看图中是否有桥。

        (以上定义参考刘汝佳<<训练指南>>P314)

         求一个无向图的所有点双连通分量可以用下面的代码,但是求一个无向图的所有边双连通分量如何求呢?

        方法1:将无向图的所有桥边标记出来,然后执行dfs,且dfs过程中不走桥边。所以每次dfs经过的点都是同一个边-双连通分量的。

        方法2:对无向图执行dfs求割点,然后对于任意点i和j,如果low[i]==low[j],那么它们属于同一个边-双连通分量(点-双连通分量内的两个点的low[]值不一定相同,自己画图验证下)。

计算点-双连通分量(无重边)的代码如下:
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<vector>
#include<stack>
using namespace std;
const int maxn=1000+10;
 
int n,m;
int bcc_cnt;
int dfs_clock;//bcc_cnt计数一共有多少个点-双连通分量
int pre[maxn];
bool iscut[maxn];
int bccno[maxn];//bccno[i]=x表示第i个顶点属于x号点双连通分量
vector<int> G[maxn],bcc[maxn];
//bcc[i]中包含了i号点-双连通分量的所有节点
 
struct Edge
{
    int u,v;
    Edge(int u,int v):u(u),v(v){}
};
stack<Edge> S;
 
int dfs(int u,int fa)
{
    int lowu=pre[u]=++dfs_clock;
    int child=0;
    for(int i=0;i<G[u].size();i++)
    {
        int v=G[u][i];
        Edge e = Edge(u,v);
        if(!pre[v])
        {
            S.push(e);
            child++;
            int lowv=dfs(v,u);
            lowu=min(lowu,lowv);
            if(lowv >= pre[u])
            {
                iscut[u]=true;
                bcc_cnt++;//注意bcc_cnt从1开始编号
                bcc[bcc_cnt].clear();
                while(true)
                {
                    Edge x=S.top(); S.pop();
                    if(bccno[x.u]!=bcc_cnt)
                    {
                        bcc[bcc_cnt].push_back(x.u);
                        bccno[x.u]=bcc_cnt;
                    }
                    if(bccno[x.v]!=bcc_cnt)
                    {
                        bcc[bcc_cnt].push_back(x.v);
                        bccno[x.v]=bcc_cnt;
                    }
                    if(x.u==u && x.v==v) break;
                }
            }
        }
        else if(pre[v]<pre[u] && v!=fa) //这个判断条件如果少了,就是WA,可修改POJ2942代码
        {
            S.push(e);
            lowu=min(lowu,pre[v]);
        }
    }
    if(fa<0 && child==1) iscut[u]=false;
    return lowu;
}
 
void find_bcc(int n)
{
    memset(pre,0,sizeof(pre));
    memset(iscut,0,sizeof(iscut));
    memset(bccno,0,sizeof(bccno));
    dfs_clock = bcc_cnt = 0;
    for(int i=0;i<n;i++)
        if(!pre[i]) dfs(i,-1);
}
int main()
{
    while(scanf("%d%d",&n,&m)==2&&n)
    {
        for(int i=0;i<n;i++) G[i].clear();
        for(int i=0;i<m;i++)
        {
            int u,v;
            scanf("%d%d",&u,&v);
            G[u].push_back(v);
            G[v].push_back(u);
        }
        find_bcc(n);
        printf("点-双连通分量一共%d个\n",bcc_cnt);
        for(int i=1;i<=bcc_cnt;i++)
        {
            printf("第%d个点-双连通分量包含以下点:\n",i);
            sort(&bcc[i][0],&bcc[i][0]+bcc[i].size()); //对vector排序,使输出的点从小到大
            for(int j=0;j<bcc[i].size();j++)
            {
                printf("%d ",bcc[i][j]);
            }
            printf("\n");
        }
    }
    return 0;
}
/*
示例输入:
6 7
1 2
2 3
1 3
3 4
4 5
3 5
5 6
输出:
点-双连通分量一共3个
第1个点-双连通分量包含以下点:
5 6
第2个点-双连通分量包含以下点:
3 4 5
第3个点-双连通分量包含以下点:
1 2 3
*/
点-双

题意:

        给你一个(保证输入无重边,无自环)无向图,然后有下面Q条询问,每条询问为:问你u点与v点之间有几条(除了首尾两点外,其他点不重复)的路径.如果有0条或1条输出0或1,如果有2条以上,输出”two or more”.

分析:

        首先如果u点与v点不连通,直接输出0即可.(用并查集实现)

        然后如果u点与v点属于同一个点-双连通分量,输出two or more.(这里有特例,两点一边的点-双连通分量应该输出1)

        剩下的所有情况表示u与v虽然连通,但是在不同的点-双连通分量类,直接输出1即可.

Q:如果该题仅要求u与v的不同路径边不同即可,情况又是如何呢?(这种情况更简单,只需考虑边-双连通分量即可且没有特例)
--------------------- 
作者:focus_best 
来源:优快云 
原文:https://blog.youkuaiyun.com/u013480600/article/details/31782327 
版权声明:本文为博主原创文章,转载请附上博文链接!


#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<vector>
#include<stack>
using namespace std;
const int maxn=5000+10;
int n,m,q;
vector<int> G[maxn], bcc[maxn];
int dfs_clock,bcc_cnt;
int pre[maxn],low[maxn],bccno[maxn];
vector<int> belong[maxn];//belong[i]表示第i个节点属于的所有点双连通分量编号
struct Edge
{
    int u,v;
    Edge(int u,int v):u(u),v(v){}
};
stack<Edge> S;
void dfs(int u,int fa)
{
    low[u]=pre[u]=++dfs_clock;
    for(int i=0;i<G[u].size();i++)
    {
        int v=G[u][i];
        if(v==fa) continue;
        Edge e=Edge(u,v);
        if(!pre[v])
        {
            S.push(e);
            dfs(v,u);
            low[u]=min(low[v],low[u]);
            if(low[v]>=pre[u])
            {
                bcc_cnt++; bcc[bcc_cnt].clear();
                while(true)
                {
                    Edge x=S.top(); S.pop();
                    if(bccno[x.u]!=bcc_cnt)
                    {
                        bcc[bcc_cnt].push_back(x.u), bccno[x.u]=bcc_cnt;
                        belong[x.u].push_back(bcc_cnt);
                    }
                    if(bccno[x.v]!=bcc_cnt)
                    {
                        bcc[bcc_cnt].push_back(x.v), bccno[x.v]=bcc_cnt;
                        belong[x.v].push_back(bcc_cnt);
                    }
                    if(x.u==u && x.v==v) break;
                }
            }
        }
        else if(pre[v]<pre[u])
        {
            S.push(e);
            low[u]=min(low[u],pre[v]);
        }
    }
}
int fa[maxn];
int find(int i)
{
    if(fa[i]==-1) return i;
    return fa[i]=find(fa[i]);
}
int main()
{
    int kase=0;
    while(scanf("%d%d%d",&n,&m,&q)==3&&n)
    {
        bcc_cnt=dfs_clock=0;
        memset(pre,0,sizeof(pre));
        memset(bccno,0,sizeof(bccno));
        memset(fa,-1,sizeof(fa));
        for(int i=0;i<n;i++) G[i].clear(),belong[i].clear();
        while(m--)
        {
            int u,v;
            scanf("%d%d",&u,&v);
            G[u].push_back(v);
            G[v].push_back(u);
            u=find(u), v=find(v);
            if(u!=v) fa[u]=v;
        }
        for(int i=0;i<n;i++)
            if(!pre[i]) dfs(i,-1);
        printf("Case %d:\n",++kase);
        while(q--)
        {
            int u,v;
            scanf("%d%d",&u,&v);
            if(find(u)!=find(v)) printf("zero\n");
            else
            {
                bool flag=false;
                //u点与v点所属的点双连通分量集合有交集,注意可能交集数<=1
                //以上结论可以分情况证明出来,注意两个割点的分量交集数<=1
                for(int i=0;i<belong[u].size()&&!flag;i++)
                for(int j=0;j<belong[v].size()&&!flag;j++)
                {
                    if(belong[u][i]==belong[v][j])
                    {
                        int num=belong[u][i];
                        if(bcc[num].size()>2)
                            printf("two or more\n"),flag=true;
                    }
                }
                if(!flag) printf("one\n");
            }
        }
    }
    return 0;
}
边-双

题意:给你一个无向连通图,问你至少需要添加几条边能使得该图是一个边双连通图?

分析:本题与POJ3352基本一样:

http://blog.youkuaiyun.com/u013480600/article/details/31004741

       首先我们用tarjan求出图中的所有的边双连通分量(对于low[i]值不同的点必然属于不同的分量),然后我们把每个分量看成一个(缩)点,就得到了一个缩点树.

       要使得这颗树变成一个边双连通的,

需要添加的边数=(度为1的点数目+1)/2.

注意:此题有重边,需要先去重.

AC代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
const int maxn=5000+10;
const int maxm=10000+10;
int n,m;
int dfs_clock;
vector<int> G[maxn];
int pre[maxn],low[maxn],degree[maxn];
void tarjan(int u,int fa)
{
    low[u]=pre[u]=++dfs_clock;
    for(int i=0;i<G[u].size();i++)
    {
        int v=G[u][i];
        if(v==fa) continue;
        if(!pre[v])
        {
            tarjan(v,u);
            low[u]=min(low[v],low[u]);
        }
        else low[u]=min(low[u],pre[v]);
    }
}
bool A[maxn][maxn];     //错误,之前写成int A[maxn][maxn]
int main()
{
    while(scanf("%d%d",&n,&m)==2&&n)
    {
        dfs_clock=0;
        memset(pre,0,sizeof(pre));
        memset(degree,0,sizeof(degree));
        for(int i=1;i<=n;i++) G[i].clear();
        memset(A,0,sizeof(A));
        for(int i=0;i<m;i++)
        {
            int u,v;
            scanf("%d%d",&u,&v);
            A[u][v]=A[v][u]=1;  //错误,之前写成A[u][v]=1;
        }
        for(int i=1;i<=n;i++)
        for(int j=i+1;j<=n;j++)if(A[i][j])
        {
            G[i].push_back(j);
            G[j].push_back(i);
        }
        tarjan(1,-1);
        for(int u=1;u<=n;u++)
        for(int i=0;i<G[u].size();i++)
        {
            int v=G[u][i];
            if(low[u]!=low[v]) //(u,v)是一条跨边双连通分量的边,即缩点树的边
                degree[low[v]]++;   //错误,这里之前写成了degree[v]++
        }
        int ans=0;
        for(int i=1;i<=n;i++)
            if(degree[i]==1) ans++;
        printf("%d\n",(ans+1)/2 );
    }
    return 0;
}
--------------------- 
作者:focus_best 
来源:优快云 
原文:https://blog.youkuaiyun.com/u013480600/article/details/31743375 
版权声明:本文为博主原创文章,转载请附上博文链接!
最大生成树

题意:

       求1号点s到n号点t的可行路径上最小值的最大值(有点拗口)也就是说从s到t的每一条可行路径上都有一条单段边的最小值,有多条路径的话就求这些最小值的最大值。

分析:

本题和POJ2263很像:http://blog.youkuaiyun.com/u013480600/article/details/37739209.

       本题最直观的做法是用并查集+二分试探.还可以用Floyd的动态规划思想做,也可以用dijkstra算法来做.这里我们用最大生成树思想来做.

       其实就是将所有边按大到小排序,然后我们依次将边加入图中,当1号点和n号点第一次连通的时候,加入的边就是从1到n路径上的承重量的最大值.(想想为什么)

AC代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=1000+10;
const int maxm=1000*1000+10;
 
struct Edge
{
    int u,v,dist;
    Edge(){}
    Edge(int u,int v,int d):u(u),v(v),dist(d){}
    bool operator<(const Edge &rhs)const
    {
        return dist >rhs.dist;//按边长从大到小排序
    }
};
 
struct Kruskal
{
    int n,m;
    Edge edges[maxm];
    int fa[maxn];
    int findset(int x){return fa[x]==-1? x:fa[x]=findset(fa[x]); }
 
    void init(int n)
    {
        this->n=n;
        m=0;
        memset(fa,-1,sizeof(fa));
    }
 
    void AddEdge(int u,int v,int dist)
    {
        edges[m++]=Edge(u,v,dist);
    }
 
    int kruskal()
    {
        sort(edges,edges+m);
 
        for(int i=0;i<m;i++)
        {
            int u=edges[i].u, v=edges[i].v;
            if(findset(u) != findset(v))
            {
                fa[findset(u)] = findset(v);
                if(findset(1) == findset(n)) return edges[i].dist;
            }
        }
        return -1;
    }
}KK;
 
int main()
{
    int T; scanf("%d",&T);
    for(int kase=1;kase<=T;kase++)
    {
        int n,m;
        scanf("%d%d",&n,&m);
        KK.init(n);
        while(m--)
        {
            int u,v,d;
            scanf("%d%d%d",&u,&v,&d);
            KK.AddEdge(u,v,d);
        }
        printf("Scenario #%d:\n%d\n\n",kase,KK.kruskal());
    }
    return 0;
}
--------------------- 
作者:focus_best 
来源:优快云 
原文:https://blog.youkuaiyun.com/u013480600/article/details/37997213 
版权声明:本文为博主原创文章,转载请附上博文链接!
次小

题意:

       给你一个n个节点m条边的无向图,问你该图的最小生成树是否唯一?如果唯一输出,树的权值,否则输出'Not Unique!'.

分析:

       其实本题就是要求该无向图的次小生成树的权值是否等于最小生成树的权值.刘汝佳的<<训练指南>>P344介绍了次小生成树以及它的求法.这里我们采取上面介绍的简单点的方法.

       一个图的次小生成树(权值<=最小生成树的权值,可能等于最小生成树的权值)肯定至少有一条边与最小生成树的一条边不同.所以我们可以枚举最小生成树上的边,然后依次用m-1条边的无向图来重新生成最小生成树,求出新生成的最小生成树中的权值最小值即为次小生成树的权值.

       注意:原题说原图是连通的,但是如果我们删除了一条边后,原图就很可能不连通了,我在这里考虑不全,WA了几次…

AC代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
const int maxn=100+10;
const int maxm=100*100+10;
 
struct Edge
{
    int u,v,dist;
    int id;//原始编号
    Edge(){}
    Edge(int u,int v,int d,int id):u(u),v(v),dist(d),id(id){}
    bool operator<(const Edge &rhs)const
    {
        return dist <rhs.dist;
    }
};
 
struct Kruskal
{
    int n,m;
    Edge edges[maxm];
    vector<int> E;//保存最小生成树上的边原始序号
    int fa[maxn];//并查集相关
    int findset(int x){ return fa[x]==-1? x: fa[x]=findset(fa[x]); }
 
    void init(int n)
    {
        this->n=n;
        m=0;
    }
 
    void AddEdge(int u,int v,int dist,int id)
    {
        edges[m++]=Edge(u,v,dist,id);
    }
 
    int kruskal(int ID)
    {
        E.clear();
        memset(fa,-1,sizeof(fa));
        int sum=0;  //最小生成树权值
        int cnt=0;  //最小生成树边数目
        sort(edges,edges+m);
 
        for(int i=0;i<m;i++)
        {
            if(edges[i].id == ID) continue;//ID边被删除
            int u=edges[i].u, v=edges[i].v;
            if(findset(u) != findset(v))
            {
                E.push_back(edges[i].id);
                fa[findset(u)] = findset(v);
                sum +=edges[i].dist;
                if(++cnt>=n-1) break;
            }
        }
        if(cnt<n-1) return -1;
        return sum;
    }
}KK;
 
int main()
{
    int T; scanf("%d",&T);
    while(T--)
    {
        int n,m;
        scanf("%d%d",&n,&m);
        KK.init(n);
        for(int i=0;i<m;i++)
        {
            int u,v,d;
            scanf("%d%d%d",&u,&v,&d);
            KK.AddEdge(u,v,d,i);
        }
        int ans1 = KK.kruskal(-1);
        int ans2 = 1e9;
 
        vector<int> E(KK.E);    //保存原图最小生成树上的
        for(int i=0;i<E.size();i++)
        {
            int tmp = KK.kruskal(E[i]);
            if(tmp ==-1) continue; //此时图不连通,不存在最小生成树
            ans2 = min(ans2,tmp);
            if(ans2 == ans1) break;
        }
        if(ans1==ans2) printf("Not Unique!\n");
        else printf("%d\n",ans1);
    }
    return 0;
}

--------------------- 
作者:focus_best 
来源:优快云 
原文:https://blog.youkuaiyun.com/u013480600/article/details/37968993 
版权声明:本文为博主原创文章,转载请附上博文链接!
树的直径

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
#define ll long long
const int maxn=1e5+5;
const int INF=0x3f3f3f3f;

struct Edge
{
    int v,l;
    int next;
} edge[maxn<<2];

int vit[maxn],d[maxn];
int head[maxn],k;
int node,ans;

void init()
{
    k=0;
    memset(head,-1,sizeof(head));
}

void addedge(int u,int v,int l)
{
    edge[k].v=v;
    edge[k].l=l;
    edge[k].next=head[u];
    head[u]=k++;

    edge[k].v=u;
    edge[k].l=l;
    edge[k].next=head[v];
    head[v]=k++;
}

void bfs(int p)
{
    queue<int>q;
    vit[p]=1;
    q.push(p);
    while(!q.empty())
    {
        int u=q.front();
        q.pop();
        for(int i=head[u]; i!=-1; i=edge[i].next)
        {
            int v=edge[i].v;
            if(vit[v]==0)
            {
                d[v]=d[u]+edge[i].l;
                vit[v]=1;
                q.push(v);
                if(d[v]>ans)
                {
                    ans=d[v];
                    node=v;
                }
            }
        }
    }
}

int main()
{
    //freopen("in.txt","r",stdin);
    init();
    int l,r,len;
    while(scanf("%d%d%d",&l,&r,&len)==3)
    {
        addedge(l,r,len);
    }

    memset(vit,0,sizeof(vit));
    memset(d,0,sizeof(d));
    ans=0;
    bfs(1);

    memset(vit,0,sizeof(vit));
    d[node]=0;
    ans=0;
    bfs(node);

    printf("%d\n",ans);

    return 0;
}
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
#define ll long long
const int maxn=1e5+5;
const int INF=0x3f3f3f3f;

struct Edge
{
    int v,l;
    int next;
} edge[maxn<<2];

int vit[maxn],d[maxn];
int head[maxn],k;
int node,ans;

void init()
{
    k=0;
    memset(head,-1,sizeof(head));
}

void addedge(int u,int v,int l)
{
    edge[k].v=v;
    edge[k].l=l;
    edge[k].next=head[u];
    head[u]=k++;

    edge[k].v=u;
    edge[k].l=l;
    edge[k].next=head[v];
    head[v]=k++;
}

void dfs(int u,int t)
{
    for(int i=head[u]; i!=-1; i=edge[i].next)
    {
        int v=edge[i].v;
        if(vit[v]==0)
        {
            vit[v]=1;
            d[v]=t+edge[i].l;
            if(d[v]>ans)
            {
                ans=d[v];
                node=v;
            }
            dfs(v,d[v]);
        }
    }
}

int main()
{
    //freopen("in.txt","r",stdin);
    init();
    int l,r,len;
    while(scanf("%d%d%d",&l,&r,&len)==3)
    {
        addedge(l,r,len);
    }

    memset(vit,0,sizeof(vit));
    vit[1]=1;
    ans=0;
    dfs(1,0);

    memset(vit,0,sizeof(vit));
    vit[node]=1;
    ans=0;
    dfs(node,0);

    printf("%d\n",ans);

    return 0;
}
广搜

#include<iostream>
#include<queue>
using namespace std;
#define MAXN 206
char map[MAXN][MAXN];
int n,m,starti,startj;
int di[4][2]= {{0,1},{1,0},{0,-1},{-1,0}};
struct lo
{
    int x,y;
    int time;
    friend bool operator<(lo a,lo b)
    {
        return a.time>b.time;
    }
};
int bfs(int nowi,int nowj,int step)
{
    priority_queue<lo>q;
    lo fir,next;
    fir.x=nowi;
    fir.y=nowj;
    fir.time=step;
    q.push(fir);
    int fx,fy;
    while(!q.empty())
    {
        fir=q.top();
        q.pop();
        for(int i=0; i<4; i++)
        {
            fx=di[i][0]+fir.x;
            fy=di[i][1]+fir.y;
            if(fx>=0&&fx<n&&fy>=0&&fy<m&&map[fx][fy]!='#')
            {
                if(map[fx][fy]=='r')
                {
                    return fir.time+1;
                }
                else if(map[fx][fy]=='.')
                {
                    next.time=fir.time+1;
                }
                else if(map[fx][fy]=='x')
                {
                    next.time=fir.time+2;
                }
                next.x=fx;
                next.y=fy;
                map[next.x][next.y]='#';
                q.push(next);
            }
        }
    }
    return -1;
}
int main()
{
 
    int i,j;
    while(cin>>n>>m)
    {
        for( i=0; i<n; i++)
        {
            for( j=0; j<m; j++)
            {
                cin>>map[i][j];
                if(map[i][j]=='a')
                {
                    starti=i;
                    startj=j;
                }
            }
        }
        int ans=bfs(starti,startj,0);
        if(ans==-1)cout<<"Poor ANGEL has to stay in the prison all his life."<<endl;
        else cout<<ans<<endl;
 
 
    }
}
#include<iostream>
 
#include<queue>
#include<string.h>
using namespace std;
int n,m,i,j,t,l,k,mark[160][160];
int dx[]={0,-1,1,0,0,-1,-1,1,1};
int dy[]={0,0,0,-1,1,-1,1,-1,1};
char e,map[160][160];
struct s
{
    int x,y;//zuobiao
};
void oil(int ax,int ay)
{
    s a,b;
    queue<s>q;
    a.x=ax;
    a.y=ay;
    q.push(a);
    mark[a.x][a.y]=1;
    while(!q.empty())
    {
        a=q.front();
        q.pop();
        for(i=1;i<=8;i++)
        {
            b.x=a.x+dx[i];
            b.y=a.y+dy[i];
            if(b.x>0&&b.y>0&&b.x<=n&&b.y<=m&&mark[b.x][b.y]==0&&map[b.x][b.y]=='@')
            {
                mark[b.x][b.y]=1;
                q.push(b);
            }
        }
    }
}
int main()
{
    while(cin>>n>>m)
    {
        t=0;
        memset(mark,0,sizeof(mark));
        if(n==0||m==0)
        return 0;
        for(l=1;l<=n;l++)
        {
            for(k=1;k<=m;k++)
            {
                cin>>e;
                map[l][k]=e;
            }
        }
        for(l=1;l<=n;l++)
        {
            for(k=1;k<=m;k++)
            {
                if(map[l][k]=='@'&&mark[l][k]==0)
                {
                    ++t;
                    oil(l,k);//广度优先搜索模板
                }
            }
        }
        cout<<t<<endl;
    }
}
最短路径

意: 

        给你n个点,m条无向边,每条边都有长度d和花费p,给你起点s终点t,要求输出起点到终点的最短距离及其花费,如果最短距离有多条路线,则输出花费最少的。

分析:

        本题直接用Dijkstra计算即可,不过更新的过程中如果对于距离相同的情况,需要对于花费,且还需要更新花费.

        其实本题就是二维目标条件,也可以把权值设计成一个二维的对象,然后自定义小于比较操作即可。

AC代码:


--------------------- 
作者:focus_best 
来源:优快云 
原文:https://blog.youkuaiyun.com/u013480600/article/details/37699799 
版权声明:本文为博主原创文章,转载请附上博文链接!


#include<cstdio>
#include<algorithm>
#include<vector>
#include<queue>
#include<cstring>
using namespace std;
#define INF 1e9
const int maxn = 1000+10;
int n,m;
 
struct Edge
{
    int from,to,dist,cost;
    Edge(int f,int t,int d,int c):from(f),to(t),dist(d),cost(c){}
};
 
struct HeapNode
{
    int d,c,u;
    HeapNode(int d,int c,int u):d(d),c(c),u(u){}
    bool operator<(const HeapNode&rhs)const
    {
        return d>rhs.d || (d==rhs.d && c>rhs.c);
    }
};
 
struct Dijkstra
{
   int n,m;
   vector<Edge> edges;
   vector<int> G[maxn];
   bool done[maxn];
   int d[maxn];
   int c[maxn];
 
   void init(int n)
   {
       this->n=n;
       for(int i=0;i<n;i++) G[i].clear();
       edges.clear();
   }
 
   void AddEdge(int from,int to,int dist,int cost)
   {
       edges.push_back(Edge(from,to,dist,cost));
       m = edges.size();
       G[from].push_back(m-1);
   }
 
   void dijkstra(int s)
   {
       priority_queue<HeapNode> Q;
       for(int i=0;i<n;i++) d[i]=INF,c[i]=INF;
       d[s]=c[s]=0;
       Q.push(HeapNode(d[s],c[s],s));
       memset(done,0,sizeof(done));
 
       while(!Q.empty())
       {
           HeapNode x= Q.top(); Q.pop();
           int u =x.u;
           if(done[u]) continue;
           done[u]= true;
 
           for(int i=0;i<G[u].size();i++)
           {
               Edge &e=edges[G[u][i]];
               if(d[e.to] > d[u]+e.dist || (d[e.to]== d[u]+e.dist&&c[e.to]>c[u]+e.cost)  )
               {
                   d[e.to]= d[u]+e.dist;
                   c[e.to]= c[u]+e.cost;
                   Q.push(HeapNode(d[e.to],c[e.to],e.to));
               }
           }
       }
   }
}DJ;
 
int main()
{
    while(scanf("%d%d",&n,&m)==2&&n)
    {
        DJ.init(n);
        for(int i=0;i<m;i++)
        {
            int u,v,d,c;
            scanf("%d%d%d%d",&u,&v,&d,&c);
            u--,v--;
            DJ.AddEdge(u,v,d,c);
            DJ.AddEdge(v,u,d,c);
        }
        int s,e;
        scanf("%d%d",&s,&e);
        s--,e--;
        DJ.dijkstra(s);
        printf("%d %d\n",DJ.d[e],DJ.c[e]);
    }
    return 0;
}
2038: [2009国家集训队]小Z的袜子(hose)
Time Limit: 20 Sec  Memory Limit: 259 MB
Submit: 7088  Solved: 3258
[Submit][Status][Discuss]
Description
作为一个生活散漫的人,小Z每天早上都要耗费很久从一堆五颜六色的袜子中找出一双来穿。终于有一天,小Z再也无法忍受这恼人的找袜子过程,于是他决定听天由命……
具体来说,小Z把这N只袜子从1到N编号,然后从编号L到R(L 尽管小Z并不在意两只袜子是不是完整的一双,甚至不在意两只袜子是否一左一右,他却很在意袜子的颜色,毕竟穿两只不同色的袜子会很尴尬。
你的任务便是告诉小Z,他有多大的概率抽到两只颜色相同的袜子。当然,小Z希望这个概率尽量高,所以他可能会询问多个(L,R)以方便自己选择。

Input
输入文件第一行包含两个正整数N和M。N为袜子的数量,M为小Z所提的询问的数量。接下来一行包含N个正整数Ci,其中Ci表示第i只袜子的颜色,相同的颜色用相同的数字表示。再接下来M行,每行两个正整数L,R表示一个询问。

Output
包含M行,对于每个询问在一行中输出分数A/B表示从该询问的区间[L,R]中随机抽出两只袜子颜色相同的概率。若该概率为0则输出0/1,否则输出的A/B必须为最简分数。(详见样例)

Sample Input
6 4
1 2 3 3 3 2
2 6
1 3
3 5
1 6
Sample Output
2/5
0/1
1/1
4/15
【样例解释】
询问1:共C(5,2)=10种可能,其中抽出两个2有1种可能,抽出两个3有3种可能,概率为(1+3)/10=4/10=2/5。
询问2:共C(3,2)=3种可能,无法抽到颜色相同的袜子,概率为0/3=0/1。
询问3:共C(3,2)=3种可能,均为抽出两个3,概率为3/3=1/1。
注:上述C(a, b)表示组合数,组合数C(a, b)等价于在a个不同的物品中选取b个的选取方案数。
【数据规模和约定】
30%的数据中 N,M ≤ 5000;
60%的数据中 N,M ≤ 25000;
100%的数据中 N,M ≤ 50000,1 ≤ L < R ≤ N,Ci ≤ N。
莫队算法:

将1~n分块
将询问离线处理,对于左端点在同一块中的按照右端点的大小排序,右端点较小的在前;
对于左端点不在同一块中的,按照左端点所在块大小排序,左端点较小的在前面;
这样排序之后,我们将排序后的区间从前往后计算,询问区间之间转移时只需要添加进来新的点和删除多余的点

莫队时间复杂度证明(最好有一些莫队的基础):

0.首先我们知道在转移一个单位距离的时候时间复杂度是O(1)

1:对于左端点在同一块中的询问:
右端点转移的时间复杂度是O(n),一共有√n块,所以右端点转移的时间复杂度是O(n√n)
左端点每次转移最差情况是O(√n),左端点在块内会转移n次,左端点转移的时间复杂度是O(n√n);

2:对于左端点跨越块的情况:
会跨区间O(√n)次;
左端点的时间复杂度是O(√n*√n)=O(n)可以忽略不计
右端点因为在跨越块时是无序的,右端点跨越块转移一次最差可能是O(n),可能跨越块转移√n次,所以时间复杂度是O(n√n)

所以总的来说莫队的时间复杂度是O(n√n);
题解:用一点儿数学知识来转移就可以了,记得约分
--------------------- 
作者:Oakley_ 
来源:优快云 
原文:https://blog.youkuaiyun.com/Oakley_/article/details/52233688 
版权声明:本文为博主原创文章,转载请附上博文链接!


#include<cmath>
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
#define N 50010
#define ll long long
ll n,m,ans;
ll c[N],pos[N],s[N];
struct pp
{ll l,r,num;ll son,mom;} a[N];
ll gcd(ll x,ll y)
{
    if(y==0) return x;
    return gcd(y,x%y);
}
bool cmp(pp u,pp v)
{
    if(pos[u.l]==pos[v.l]) return u.r<v.r;
    return u.l<v.l;
}
bool cmp1(pp u,pp v)
{return u.num<v.num;}
void init()
{
    for(ll i=1;i<=n;i++) scanf("%lld",&c[i]);
    ll len=sqrt(n);
    for(ll i=1;i<=n;i++) pos[i]=(i-1)/len+1;
    for(ll i=1;i<=m;i++)
    {
        scanf("%lld%lld",&a[i].l,&a[i].r);
        a[i].num=i;
    }
    sort(a+1,a+m+1,cmp);
}
void modify(ll p,ll add)
{
    ans-=s[c[p]]*s[c[p]];
    s[c[p]]+=add;
    ans+=s[c[p]]*s[c[p]];
}
void modui()
{
    ll l=1,r=0;
    for(ll i=1;i<=m;i++)
    {
        for(;r<a[i].r;r++) modify(r+1,1);
        for(;r>a[i].r;r--) modify(r,-1);
        for(;l<a[i].l;l++) modify(l,-1);
        for(;l>a[i].l;l--) modify(l-1,1);
        if(a[i].l==a[i].r) a[i].son=0,a[i].mom=1;
        else
        {
            a[i].son=ans-(a[i].r-a[i].l+1);
            a[i].mom=(a[i].r-a[i].l+1)*(a[i].r-a[i].l);
            ll k=gcd(a[i].son,a[i].mom);
            a[i].son/=k,a[i].mom/=k;
        }
    }
    sort(a+1,a+m+1,cmp1);
}
int main()
{
    scanf("%lld%lld",&n,&m);
    init();
    modui();
    for(int i=1;i<=m;i++) printf("%lld/%lld\n",a[i].son,a[i].mom);
}
第几大

#include<iostream>
#include<algorithm>
using namespace std;
int main()
{
    int a[]={1,3,4,5,2,6,8,7,9};
    int i;
    cout<<"数列例如以下:"<<endl;
    for(i=0;i<9;i++)
       cout<<a[i]<<" ";
    nth_element(a,a+2,a+9);
    cout<<endl<<"排名第几号: "<<a[1]<<endl; //注意下标是从0開始计数的
    return 0;
}
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值