拓扑排序 图

题目来源:

https://hihocoder.com/problemset/problem/1175

http://acm.hdu.edu.cn/showproblem.php?pid=2647

第一题是一个最基本的拓扑排序,第二题可以理解为第一题的逆向思维。那么从第一题开始说起。

在此之间先了解几个概念

有向无环图

比如a可以到b但是b不能到a,那么我们称a到b是有向的,相反的a和b之间可以互相抵达则是无向的,无向图在并查集的有涉及到。

这里的第一题是保证数据是有向无环图的,第二题不保证,怎么处理后面细说。

vecter

这题是我第一用vector,关于vector怎么使用会在代码中注释出来,这里就不细说。

 说完了这些来看第一题病毒的传播,这一题要怎么实现题目都已经说了,这里简单说一下,给出病毒传输的路径,病毒不会逆向传播,从开端开始,把所有的开端压入队列,之后对开端能达到的病毒进行遍历,统计他们的病毒数目,并且每次将入度(即为有几个点能到达这个点)为0的病毒压入队列,直到遍历完所有的病毒位置。

下面通过代码来细说

#include<iostream>
#include<queue>
#include<vector>
#define m 142857
using namespace std;
queue<int> q;//创建队列用来遍历点
vector<int> vec[100005];//创建数组用来存该点可以到哪几个点
int num[100005];//用来存该点的病毒数目
int tree[100005];//存该点的入度
int main()
{
    int N,M,K,a,b,cnt=0;
    cin>>N>>M>>K;
     for(int i=1;i<=K;i++)
     {
         cin>>a;
         num[a]++;
     }
     for(int i=1;i<=M;i++)
     {
         cin>>a>>b;
         vec[a].push_back(b);//a能到b,所以将b压入a数组
         tree[b]++;//b的入度加一
     }
     for(int i=1;i<=N;i++)//找到所有的起点即入度为0的点
        if(tree[i]==0)
        q.push(i);
     while(!q.empty())//开始对每一个数据进行处理
     {
         a=q.front();
         q.pop();
         cnt+=num[a];//统计总共的病毒数
         cnt%=m;
         for(int i=0;i<vec[a].size();i++)//对a能到达的每一个点进行处理
         {
             b=vec[a][i];
             num[b]+=num[a];//统计病毒数
             tree[b]--;//入度减一
             num[b]%=m;
             if(tree[b]==0)//入度为零,压入队列
                q.push(b);
         }
     }
     cout<<cnt%m;
    return 0;
}

 这是第一题,下面来看第二题,先说下题意一个分配工资的问题,题目给出所有人物之间的比较关系,要求输出最少的工资,若不满足所有人的要求,则输出-1。

那么这一题跟第一题很类似,但是又有几个不同的地方,下面我举例子来进行说明,第一个是什么时候输出-1。

看这个例子,3要比2多,4要比3多,2要比4多,很显然不成立,所以输出-1,那么也就说明了一旦成环,就不成立。在程序里如何判断就是根据有没有遍历全部的数,我们可以看出起点只有1,但是当1处理到2的时候,2的入度为2,减一后为1还是不为零,不会进入队列,所以程序到此结束,那么2,3,4都没有遍历,根据环的特性我们可以知道拓扑排序的方法是不会进入环的,那么我们只要记录一共处理了多少个点,和总的点判断就可以了。

还有一个不同的地方

这个例子里,2要比1多,3要比1多,那么假设1需要的工资为0的话,如果按照第一的方式,那么2和3需要的工资就为1,那么需要的总工资为2,这显然是错误的,正确的结果应该是2和3需要的为0,而1需要的工资为1,总工资为1,所以这一题就要反向建图,这是第二个不同点。

下面看完整代码

#include<iostream>
#include<vector>
#include<queue>
#include<stdio.h>
#include<cstring>
using namespace std;
int n,m,num,ans;
vector<int> vec[10005];
queue<int> q;
int main()
{
    int a,b;
    int tree[10005];
    int cnt[10005];//假设最小的工资为0,在此基础上每当需要增加的时候就加一
    while(scanf("%d",&n)!=EOF)
    {
        cin>>m;
        ans=888*n;//先计算出所有人的基本工资,方便后期计算。
        //因为是不定组输入,所以这里每一组数据都需要初始化
        memset(tree,0,sizeof(tree));//初始化
        memset(cnt,0,sizeof(cnt));//初始化
        for(int i=0;i<=10004;i++)//初始化
        {
            vec[i].clear();
        }
        num=0;
        for(int i=1;i<=m;i++)
        {
            cin>>a>>b;
            vec[b].push_back(a);//注意,这里是反向,把a设为b能到达的点
            tree[a]++;//a的入度加一
        }
        for(int i=1;i<=n;i++)//统计所有的起点
            if(tree[i]==0)
            {
                q.push(i);
                num++;
            }
        while(!q.empty())//处理所有的数据
        {
            a=q.front();
            q.pop();
            for(int i=0;i<vec[a].size();i++)
            {
                b=vec[a][i];
                cnt[b]=cnt[a]+1;//因为b工资要比a多,所以在a的基础上加一
                tree[b]--;//b的入度减一
                if(tree[b]==0)//若入度为零,就压入队列
                {
                    q.push(b);
                    num++;
                }
            }
        }
        if(num!=n)//查是不是搜索到了所有的点,若没有搜索到,则说明存在成环的情况
            cout<<"-1"<<;
        else
        {
            for(int i=1;i<=n;i++)//遍历所有的点,将所有需要增加的工资加初始工资里
            {
                ans+=cnt[i];
            }
            cout<<ans<<endl;
        }
    }
    return 0;
}
原文地址:https://www.cnblogs.com/zlhdbk/p/10775804.html