2021-12-9 关于校赛的复盘

1.最大食物链计数

题目背景

你知道食物链吗?Delia 生物考试的时候,数食物链条数的题目全都错了,因为她总是重复数了几条或漏掉了几条。于是她来就来求助你,然而你也不会啊!写一个程序来帮帮她吧。

题目描述

给你一个食物网,你要求出这个食物网中最大食物链的数量。

(这里的“最大食物链”,指的是生物学意义上的食物链,即最左端是不会捕食其他生物的生产者,最右端是不会被其他生物捕食的消费者。)

Delia 非常急,所以你只有 11 秒的时间。

由于这个结果可能过大,你只需要输出总数模上 8011200280112002 的结果。

输入格式

第一行,两个正整数 n、mn、m,表示生物种类 nn 和吃与被吃的关系数 mm。

接下来 mm 行,每行两个正整数,表示被吃的生物A和吃A的生物B。

输出格式
一行一个整数,为最大食物链数量模上 8011200280112002 的结果。

输入样例

5 7
1 2
1 3
2 3
3 5
2 5
4 5
3 4

输出样例

5

说明/提示
各测试点满足以下约定:
约定.png
【补充说明】

数据中不会出现环,满足生物学的要求。

问题分析

很明显,题目所说的生物链很契合无环有向图(DAG)图的特征,我们最终的任务是统计生物链个数,就可以转换成统计DAG图的路径条数问题,接下来解这道题的话,思考三个问题:1.选用什么存储结构表达DAG图,2.从入度为0的点到出度为0的节点的极长链怎么通过图像来表示出来,3.最后的路径数该怎么算,取模怎么取??

第一、图像的储存结构,我们熟知的有两种,一种是邻接矩阵,另外一种是邻接表。选用邻接矩阵或许不失为一种简单方法,但是很多空间会浪费。邻接表在空间上能更优化,但是实现比较麻烦。还有一种是链式前向星,思路上和邻接表很相似,但是可能难理解一点,后续需要研究一波。

第二、此题题目上说前面没有生物可以吃它,后面他没有生物可以吃,前面入度为0,后面出度为0,这种特征选用拓扑排序,拓扑排序就是通过入度为0的顶点来将一个网构造出“拓扑有序序列”,与题目中的一条生物链很相似,所以可以尝试这种方法。

我的大致思路:
1.先初始化:将入度为0的顶点放入队列当中
2.然后陆续出列,删去这个顶点的弧,具体代码实现也很简单,比如u->v的边,将u的出度减1,v的入度减1
3.然后判断,要是这个顶点的入度为0,就把他入队,等待后续出队操作(删去弧边),另外注意一下,如果他出度为0了,那么这个顶点i的dp[i]就是以i为终点的所有路径条数决策数。后续所有的路径数就累加即可(所有出度为0的点为终点的顶点的路径和)
4.就这样一直循环,直到最后队列为空为止(此时所有顶点都出来了,因为没有回路,就不存在没有前驱的节点)

第三、路径条数怎么算?这里就是在拓扑排序的过程中用DP就可以了,状态转移方程是dp[v]=dp[v]+dp[u],注意加完以后取模就行了,因为最后的值很大,所以要注意按照题目要求加完以后取模。

二维数组构造邻接矩阵代码

一些说明:
本来是用int存储邻接矩阵的,但是因为此题没有权值,所以我们只需要知道他是否连通,所以我们可以用char,short或是bool类型在一定程度上减少空间的占用,从而优化代码。
删除这条边就是让边不连通即可,比如这里我是令其为false

a[x][y] = false;

参考代码

#include <stdio.h>
#include <queue>
using namespace std;

#define maxn 5005

const int mod = 80112002;

bool a[maxn][maxn];      //邻接矩阵
int in[maxn], out[maxn]; //顶点的入度,出度
int dp[maxn], n, m, ans; //到达此顶点的路径数,顶点数,边数,所有路径数之和
queue<int> q;

//输入,并设定好相应入度出度以及邻接矩阵,并将入度为0的放入队列,等待后续处理
void init()
{
    scanf("%d%d", &n, &m);
    int x, y;
    for (int i = 0; i < m; i++)
    {
        scanf("%d%d", &x, &y);
        a[x][y] = true;
        out[x]++;
        in[y]++;
    }
    for (int i = 1; i <= n; i++)
    {
        if (in[i] == 0)
        {
            dp[i] = 1;
            q.push(i);
        }
    }
}

void slove()
{
    while (!q.empty())
    {
        int front = q.front();
        q.pop();
        for (int i = 1; i <= n; i++)
        //寻找与front相连的顶点,做后续操作
        {

            if (!a[front][i])
                continue;
            a[front][i] = 0;
            dp[i] = dp[i] + dp[front];
            dp[i] %= mod;
            out[front]--;
            in[i]--;
            if (in[i] == 0)
            {
                if (out[i] == 0)
                {
                    ans += dp[i];
                    ans %= mod;
                    continue;
                }
                q.push(i);
            }
        }
    }
    printf("%d\n", ans);
}

int main()
{
    init();
    slove();
    return 0;
}

构造前向星的代码

(可参考此网站

我的一些理解
结构体定义(注:这是C++写法,没有typedef时,后面直接跟上结构变量)参考网站戳这

struct edge
{
    int to, weight, next;
    //to是边的终点,weight是边的权重,next是以边的起点u时的上一条边的编号
} edge[maxn];
//我所输入的每一条边的序号信息存入此结构体数组中

head[ i ]数组是以i为起点的最后一条边序号
我们是通过head [ i ]不断通过next(同样以i为起点的上一条边)来访问起点为i的边,用edge[i].to表示这条边的终点,最后head[i]=-1时结束访问,这是不是和邻接表的链式结构很相似呢?

最后附上我的参考代码

#include <stdio.h>
#include <queue>
#define maxn 5000005

using namespace std;

const int mod = 80112002;

struct edge
{
    int to, weight, next;
} edge[maxn];

queue<int> q;
int in[maxn], out[maxn], dp[maxn], head[maxn];
int m, n, cnt, ans; // cnt动态记录边的个数.ans记录结果

//初始化:以 i 为起点的最后一条边的编号初始化为-1。(后续前向星的终止条件)
void init()
{
    for (int i = 0; i < m; i++)
        head[i] = -1;
    cnt = 0;
}

void add_edge(int u, int v)
{
    edge[cnt].to = v;
    edge[cnt].next = head[u];   //第cnt+1条边的上一条边是以u为起点的最后一条边
    head[u] = cnt++;
}

void slove()
{
    while (!q.empty())
    {
        int front = q.front();
        q.pop();
        int i;
        for (i = head[front]; i != -1; i = edge[i].next)
        {
            dp[edge[i].to] = dp[edge[i].to] + dp[front];
            dp[edge[i].to] %= mod;
            out[front]--;
            in[edge[i].to]--;
            if (in[edge[i].to] == 0)
            {
                if (out[edge[i].to] == 0)
                {
                    ans += dp[edge[i].to];
                    ans %= mod;
                    continue;
                }
                q.push(edge[i].to);
            }
        }
    }
    printf("%d\n", ans);
}

int main()
{
    int u, v;
    //输入
    scanf("%d%d", &n, &m);

    //前向星构造
    init();
    for (int i = 0; i < m; i++)
    {
        scanf("%d%d", &u, &v);
        add_edge(u, v);
        out[u]++;
        in[v]++;
    }

    //拓扑排序,所有入度为0的点放入队列等待处理,并设置到此节点的决策数为1种
    for (int i = 1; i <= n; i++)
    {
        if (in[i] == 0)
        {
            dp[i] = 1;
            q.push(i);
        }
    }
    slove();
    return 0;
}

ps:小白不才,最后欢迎大佬指正

posted @ 2021-12-09 00:09  yuezi2048  阅读(37)  评论(0编辑  收藏  举报