P2661 [NOIP2015 提高组] 信息传递 题解

题目传送门

一、解题思路

1、用拓扑排序干掉非环结点

2、用\(dfs\)或者\(bfs\)找出最小环的长度

二、拓扑排序+dfs 解法

1、vector邻接表实现

#include <bits/stdc++.h>

using namespace std;

/**
思路:其实就是求最小环。每个点的出度都是1,因此构成的图要么是一条链+一个环,要么是几个环,
通过拓扑可以消去链状的部分,对环的部分dfs算最小环即可。
*/
const int N = 2e5 + 10;

int n;                  //n个同学
int ans = 0x3f3f3f3f;   //答案,初始值为极大值
vector<int> edge[N];    //邻接表
int ind[N];             //入度数组
queue<int> q;           //拓扑排序用的队列
bool st[N];             //是不是已经被排除掉

//拓扑排序[这是一个拓扑排序的模板,但里面扩充了本题的st[i]数组标识 ]
void topsort() {
    //入度为0的结点入队列
    for (int i = 1; i <= n; i++) if (!ind[i]) q.push(i);
    //图的广度优先遍历
    while (!q.empty()) {
        int x = q.front();
        q.pop();
        //标识非环中结点
        st[x] = true;
        //遍历每条出边
        for (int i = 0; i < edge[x].size(); i++) {
            int y = edge[x][i];
            ind[y]--;
            if (!ind[y]) q.push(y);//在删除掉当前结点带来的入度后,是不是入度为0了,如果是将点y入队列
        }
    }
}

/**
 功能:求图中包含点u的环的长度
 参数: u   结点
       len 长度
 */
void dfs(int u, int len) {
    //标识探测过了
    st[u] = true;
    //遍历所有u开头的边
    for (int y:edge[u]) {
        //如果这个点还没有被探测过,那么继续探索
        if (!st[y]) dfs(y, ++len);
        else { //到这里是发现了环!因为现在在邻接表中只剩下了环,其它的链路都被拓扑排序干掉了,
            // 结果还是出现访问过的结点,就肯定是发现环了
            ans = min(ans, len); //这是递归的出口.此处和其它的递归不一样啊,递归出口居然在代码区,
            // 而不是在一进递归的位置~
            return;
        }
    }
}

int main() {
    //创建有向图
    cin >> n;
    for (int i = 1; i <= n; i++) {
        int u;
        cin >> u;
        //邻接表方式
        edge[i].push_back(u);
        //入度++
        ind[u]++;
    }
    //拓扑排序
    topsort();

    //到这里,st[i]=false的就应该是环中结点,对环的部分dfs算最小环即可
    for (int i = 1; i <= n; i++) if (!st[i]) dfs(i, 1);
    cout << ans << endl;
}

2、链式前向星实现

#include <bits/stdc++.h>

using namespace std;
const int INF = 0x3f3f3f3f;
//拓扑排序+链接式前向星实现
/**
思路:其实就是求最小环。每个点的出度都是1,因此构成的图要么是一条链+一个环,要么是几个环,通过拓扑可以消去链状的部分,
对环的部分dfs算最小环即可。
*/
const int N = 2e5 + 10;

int n, idx, ans = INF;
int head[N];    //链表头数组

int in[N];      //入度数组
queue<int> q;   //拓扑排序用的队列

struct Edge {
    int to, next;
} edge[N]; //边数,也不可能多于结点数,因为这里是指每个结点引出的边数集合

bool st[N];

//从u向v连一条边,本题无权值概念,头插法
void add(int u, int v) {
    // 进来先++是非常优美的操作,省去了初始化head[i]=-1!~~~,不过注意,遍历的方式有所变动,第二个条件是i,而不是i!=-1
    edge[++idx].to = v;  //因为idx默认值是,进来先++,就是第一个可用值是edge[1],edge[0]丢弃不使用的意思
    edge[idx].next = head[u];
    head[u] = idx;
    //入度++
    in[v]++;
}

//拓扑排序
void topsort() {
    //入度为0的结点入队列,进行拓扑排序
    for (int i = 1; i <= n; i++) if (!in[i]) q.push(i);

    //拓扑排序
    while (!q.empty()) {
        int u = q.front();
        q.pop();
        //不是环中结点
        st[u] = true;
        //遍历每条出边
        for (int j = head[u]; j; j = edge[j].next) {
            int y = edge[j].to;
            if (!--in[y]) q.push(y);//在删除掉当前结点带来的入度后,是不是入度为0了,如果是将点y入队列
        }
    }
}

/**
 功能:求DAG中包含点u的环的长度
 参数: u   结点
       len 长度
 */
void dfs(int u, int len) {
    //标识探测过了
    st[u] = true;
    //遍历所有u开头的边
    for (int i = head[u]; i; i = edge[i].next) {
        int y = edge[i].to;
        //如果这个点还没有被探测过,那么,计算长度
        if (!st[y]) dfs(y, ++len);
        else { //到这里是发现了环!
            ans = min(ans, len);
            return; //第一次发现的就是终点
        }
    }
}

int main() {
    cin >> n;
    for (int u = 1; u <= n; u++) {
        int x;
        cin >> x;
        add(u, x);
    }
    //拓扑排序
    topsort();

    //到这里,st[i]=false的就应该是环中结点,对环的部分dfs算最小环即可
    for (int i = 1; i <= n; i++) if (!st[i]) dfs(i, 1);
    cout << ans << endl;
}

posted @ 2021-08-11 09:07  糖豆爸爸  阅读(101)  评论(0编辑  收藏  举报
Live2D