#牛客网 [HAOI2016] 食物链 拓扑排序 + 邻接表 / 链式前向星 + dp

第一种 : 拓扑排序 + 链式前向星 + dp 

链式前向星不会的话看我这篇博客 : https://blog.csdn.net/weixin_43851525/article/details/90411677

说一下里面的 dp[v] += dp[u], 这个要用到动态规划的思想,这一步的结果由上一步的来决定,我们想要确定一个有向图有多少个“食物链”, 可以确定每个出度为0的点(我把它称为“最终出口”)所连食物链之和,也就是把一张图以n个最终出口为基准,拆成 n个食物链,这样的话求和就OK,不清楚的话可以自己画一张图,看看把多个最终出口的图拆分成几张图,就可以很清楚的看到动态规划的步骤了~

下面是链式前向星的AC代码:

#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 5;

struct node
{
    int to, next; //链式前向星操作,不懂看另一篇博客
}e[maxn << 1];
int head[maxn], in[maxn], out[maxn]; // in表示出度,out表示入度
int dp[maxn], n, m, cnt = 0, ans = 0; // dp表示动态存储以该点为终点的食物链数目
void add (int from, int to) {
    e[++cnt].to = to;
    e[cnt].next = head[from];
    head[from] = cnt; // 存图
}
void topsort() {
    queue <int> q;
    for (int i = 1; i <= n; i++) {
        if (!in[i]) {    //将入度为0的点存入队列,拓扑排序的基本方法
            q.push(i);
            if (out[i]) dp[i] = 1; //将有“出口”的点的此时的食物链条数初始化为1
        }
    }
    while (!q.empty()) {
        int u = q.front();  //弹出
        q.pop();
        for (int i = head[u]; i != -1; i = e[i].next) { // 链式前向星遍历该点所连点
            int v = e[i].to; 
            in[v]--;  // 删除的点的入度更新
            dp[v] += dp[u];  //上面说过
            if (!in[v]) q.push(v);  //存入
        }
    }
    for (int i = 1; i <= n; i++) {
        if (!out[i]) ans += dp[i]; // 将所有 “最终出口” 的食物链数目相加即为答案
    }
    cout << ans << endl;
}

int main()
{
    memset(head, -1, sizeof(head));
    cin >> n >> m;
    for (int i= 0; i < m; i++) {
        int ui, vi;
        cin >> ui >> vi;
        add (ui, vi);
        in[vi]++, out[ui]++;
    }
    topsort();
    return 0;
}

 

下面是邻接表的AC代码,如果理解了本题原理的话邻接表就更简单了  

 

#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 5;

int head[maxn], in[maxn], out[maxn];
int dp[maxn], n, m, cnt = 0, ans = 0;
vector <int> e[maxn];
void topsort() {
    queue <int> q;
    for (int i = 1; i <= n; i++) {
        if (!in[i]) {
            q.push(i);
            if (out[i]) dp[i] = 1;
        }
    }
    while (!q.empty()) {
        int u = q.front();
        q.pop();
        for (auto it = e[u].begin(); it != e[u].end(); it++) {
            int v = *it;
            in[v]--;
            if (!in[v]) q.push(v);
            dp[v] += dp[u];
        }
    }
    for (int i = 1; i <= n; i++) {
        if (!out[i]) ans += dp[i];
    }
    cout << ans << endl;
}

int main()
{
    cin >> n >> m;
    for (int i = 0; i < m; i++) {
        int ui, vi;
        cin >> ui >> vi;
        e[ui].push_back(vi);
        in[vi]++, out[ui]++;
    }
    topsort();
    return 0;
}

PS : 一位学了四年算法的大佬说,链式前向星和邻接表相比,写法稍微麻烦,速度更快,内存节省5倍,所以具体用哪种根据自己情况来吧~

 

 

 

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
这道题目还可以使用树状数组或线段树来实现,时间复杂度也为 $\mathcal{O}(n\log n)$。这里给出使用树状数组的实现代码。 解题思路: 1. 读入数据; 2. 将原数列离散化,得到一个新的数列 b; 3. 从右往左依次将 b 数列中的元素插入到树状数组中,并计算逆序对数; 4. 输出逆序对数。 代码实现: ```c++ #include <cstdio> #include <cstdlib> #include <algorithm> const int MAXN = 500005; struct Node { int val, id; bool operator<(const Node& other) const { return val < other.val; } } nodes[MAXN]; int n, a[MAXN], b[MAXN], c[MAXN]; long long ans; inline int lowbit(int x) { return x & (-x); } void update(int x, int val) { for (int i = x; i <= n; i += lowbit(i)) { c[i] += val; } } int query(int x) { int res = 0; for (int i = x; i > 0; i -= lowbit(i)) { res += c[i]; } return res; } int main() { scanf("%d", &n); for (int i = 1; i <= n; ++i) { scanf("%d", &a[i]); nodes[i] = {a[i], i}; } std::sort(nodes + 1, nodes + n + 1); int cnt = 0; for (int i = 1; i <= n; ++i) { if (i == 1 || nodes[i].val != nodes[i - 1].val) { ++cnt; } b[nodes[i].id] = cnt; } for (int i = n; i >= 1; --i) { ans += query(b[i] - 1); update(b[i], 1); } printf("%lld\n", ans); return 0; } ``` 注意事项: - 在对原数列进行离散化时,需要记录每个元素在原数列中的位置,便于后面计算逆序对数; - 设树状数组的大小为 $n$,则树状数组中的下标从 $1$ 到 $n$,而不是从 $0$ 到 $n-1$; - 在计算逆序对数时,需要查询离散化后的数列中比当前元素小的元素个数,即查询 $b_i-1$ 位置上的值; - 在插入元素时,需要将离散化后的数列的元素从右往左依次插入树状数组中,而不是从左往右。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值