拓扑排序 & 关键路径

1、 拓扑排序

定义: 对一个有向无环图(Directed Acyclic Graph简称DAG) G进行拓扑排序,是将G中所有顶点排成一个线性序列,使得图中任意一对顶点u和v,若边<u,v>∈E(G),则u在线性序列中出现在v之前。

下面是阳阳的起床到9#504过程, 你能写出属于阳阳起床流程的拓扑序列嘛

正确顺序: 睁眼 -> 衬衣 -> 袜子 -> 外套 -> 鞋子 -> 出门 (不唯一) 

        拓扑序列:   V1 -> V6 -> V3 -> V4 -> V2 -> V5 (不唯一)

#include <bits/stdc++.h>
using namespace std;

const int N = 100010;
int d[N], q[N], e[N], ne[N], h[N], idx;
int n, m;

void add (int a, int b)   // 邻接表存边
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
}

bool topsort ()
{
    int hh = 0, tt = -1;
    
    for (int i = 1; i <= n; i ++) 
        if (!d[i]) q[++ tt] = i;    // 先把入度为0 的存入队列
    
    while (hh <= tt)   // 数组模拟队列
    {
        int t = q[hh ++];
        
        for (int i = h[t]; i != -1; i = ne[i])
        {
            int j = e[i];
            if (-- d[j] == 0)  // 遍历此点所有联通的点, 如果去掉此点后入度为零, 即可继续存入
                q[++ tt] = j;
        }
    }
    return tt == n - 1;   // 队列里可以保证所有均为拓扑排序, 若个数就是n个, 就说明可以构成拓扑排序
}

int main ()
{
    cin >> n >> m;
    memset (h, -1, sizeof h);
    
    while (m --)
    {
        int a, b;
        cin >> a >> b;
        add (a, b);
        d[b] ++;   // 入度++   
    }
    
    if (!topsort()) puts("-1");
    else for (int i = 0; i < n; i ++) cout << q[i] << " ";   // 队列里的元素恰好为拓扑排序序列
    return 0;
}

题目链接-ACwing-- problem-164

代码:

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 3e4 + 10;
vector<int> q[N];//邻接表建图
int a, b;
int l, r;
map<pair<int, int>, int> mp;//输入时去除重边
struct node
{
    int x;
    int j;
};
int arr[N];

bitset<30001> dis[30001];//存路径之前有几个
queue<node> p;
void t()//拓扑排序函数
{
    while (!p.empty())
    {
        node temp = p.front();//取队首
        p.pop();
        int len = q[temp.x].size();
        for (int i = 0; i < len; i++)
        {
            arr[q[temp.x][i]]--;//入度减一
            dis[q[temp.x][i]] |= dis[temp.x];//更新到该点的节点数
            if (arr[q[temp.x][i]] == 0)//入度为0加入队列
            {
                int tem = dis[q[temp.x][i]].count();
                p.push({q[temp.x][i], tem});//不能将dis[q[temp.x][i]].count()直接放进去,会报错,先赋给tem
            }
        }
    }
    return;
}
signed main()
{

    cin >> a >> b;
    for (int i = 1; i <= a; i++)
    {
        dis[i][i] = 1;
    }
    for (int i = 0; i < b; i++)
    {
        scanf("%d%d", &l, &r);
        q[r].push_back(l);//反向入度,倒推
        if (mp[{r, l}] != 1)//重复点不增加入度
        {
            arr[l]++;
        }
        mp[{r, l}] = 1;
    }
    for (int i = 1; i <= a; i++)
    {
        sort(q[i].begin(), q[i].end());//排序
        q[i].erase(unique(q[i].begin(), q[i].end()), q[i].end());//排序后才能用这个方法去重边
    }

    for (int i = 1; i <= a; i++)
    {
        if (arr[i] == 0)//入度为零加入,多次加入,一次加入也行,把函数放外面
        {
            p.push({i, 1});
            t();//函数,拓扑排序
        }
    }

    for (int i = 1; i <= a; i++)
    {
        cout << dis[i].count() << endl;
    }

    return 0;
}

2、关键路径

 最长路问题:   题目链接SDUTACM -problem-2498

#include <bits/stdc++.h>
using namespace std;
#define ios ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
 
const int N = 10010, M = 50010;
 
int h[N], e[M], w[M], ne[M], idx;
int dist[N];
bool st[N];
int r[N], C[N];
int path[N];  // 记录其father
int npath[N];  // 记录这条路径的条数, 处理字典序
int s, en; // 起点终点
vector<int> a;
 
void add(int a, int b, int c) {
    e[idx] = b, ne[idx] = h[a]++, w[idx] = -1 * c, h[a] = idx++;  // 最长路问题转化为权值取负的最短路问题 spfa
}
 
int spfa(int x) {
    // ios;
    memset(dist, 0x3f, sizeof dist);
 
    dist[x] = 0;
    queue<int> q;
    q.push(x);
    st[x] = true;
 
    while (q.size()) {
        int t = q.front();
        q.pop();
 
        st[t] = false;
        for (int i = h[t]; i != -1; i = ne[i]) {
            int j = e[i];
 
            if (dist[j] > dist[t] + w[i] ||
                dist[j] == dist[t] + w[i] && (npath[j] == npath[t] + 1 && path[j] > j || j > t)) {  // 取字典序较小的
                path[j] = t;
                npath[j] = npath[t] + 1;
                dist[j] = dist[t] + w[i];
                if (!st[j]) q.push(j), st[j] = true;
            }
        }
    }
    return dist[en];
}
 
int main() {
    int n, m;
    ios;
    while (cin >> n >> m) {
        idx = 0;
        memset(st, 0, sizeof st);
        memset(path, 0, sizeof path);
        memset(C, 0, sizeof C);
        memset (npath, 0, sizeof npath);
        memset(r, 0, sizeof r);
        a.clear();
        memset(h, -1, sizeof h);
        while (m--) {
            int a, b, c;
            cin >> a >> b >> c;
            add(a, b, c);
            r[b]++;  // 入度
            C[a] ++; // 出度
        }
 
        for (int i = 1; i <= n; i++)
            if (!r[i])
                s = i;
            else if (!C[i])
                en = i;
 
        int res = -1 * spfa(s);
 
        cout << res << endl;
        while (path[en]) {    // 弹出路径
            a.push_back(en);
            a.push_back(path[en]);
            en = path[en];
        }
        int x = 1;
        for (int i = a.size() - 1; i >= 0; i--) {
            if (x % 2 != 0)
                cout << a[i] << " ";     // 输出格式
            else
                cout << a[i] << '\n';
            x++;
        }
    }
    return 0;
}

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值