NKOJ-4092 [HAOI2016] 食物链<附TopSort理解>

17 篇文章 0 订阅
10 篇文章 1 订阅

P4092[HAOI2016]食物链

时间限制 : - MS 空间限制 : 165536 KB
评测说明 : 1s

问题描述

    给你n个物种和m条能量流动关系,求其中的食物链条数。
    物种的名称为从1到n编号
    M条能量流动关系形如

    a1 b1
    a2 b2
    a3 b3
    ......
    am-1 bm-1
    am bm

    其中ai bi表示能量从物种ai流向物种bi,注意单独的一种孤立生物不算一条食物链

输入格式

    第一行两个整数n和m,接下来m行每行两个整数ai bi描述m条能量流动关系。
    (数据保证输入数据符号生物学特点,且不会有重复的能量流动关系出现)
    1<=N<=100000 0<=m<=200000

    题目保证答案不会爆 int

输出格式

一个整数即食物网中的食物链条数

样例输入

    10 16
    1 2
    1 4
    1 10
    2 3
    2 5
    4 3
    4 5
    4 8
    6 5
    7 6
    7 9
    8 5
    9 8
    10 6
    10 7
    10 9

样例输出

    9

这道题目我是不想写过程的
然而一不小心WA了一次

关于这个假的食物链

我只想知道这个食物链底端的生物是怎么活下来的
还有为什么会有单独的生物==自生自灭??

这道题目有两个解法

方法一:TOP排序

很简答撒
主要还是讲一讲TOP的原理

    (呼一口气)
    来吧!(打这个东西是真的很累...因为我没有图)

    TOP的使用目的是为了找出一张图里头从起点出发被遍历的顺序
        且一条边的终点一定比起点晚遍历
    比如对于下图(脑补图):
        1->2
        2->3
        1->4
        4->3
    从1出发
    2、4是第二组被遍历的
    3是最后被遍历的
    那么如何确定被遍历的顺序呢??

非常简单

需要知道的概念

出度 从这个点出发的边的条数
入度 到这个点的边的条数

把入度为0的点加到一个stack里
然后挨个从stack里读取点point
    此时就可以确定point的遍历次序
    然后把point的边全删掉,同时把边的终点的点的入度减掉
    当这个点入度为0时就加进stack

目的:

当这个点入度不为零时,说明这个点还有边的起点没有被遍历到
但是当这个点入度为零时,说明终点为它的边的起点全部都已经被标号了
这时它就可以被标号了,于是以它为起点的边的终点被遍历的条件就减了一项

注意:

为了避免社会上普遍出现的因为按拼音排序导致的排名先后顺序存在bug的问题,我们此处的顺序,纯看命==
就算你的点编号是1,其它和你同时编号的点一样可以在你前面
    没错我们做到了点点平等,每个点都可以当第一
    (比如我举的例子里的2、4,谁先被读到谁在前面)
所以如果你很反平等主义的话你得自己想办法让它们按顺序标号

关于这道题目

很神奇的是,这道题目,完全不需要排序
它只要TOP
没错你可能没见过这么势利的题目
只要TOP不要排序

然而你相信我的鬼话了么
上面这个算法是自带排序的
怎么可能只要TOP不要排序呢

对于这道题目

当一个点到真•终点的路径为n种时(比如终点到终点的路径为一种)!!此处的终点是真•终点
那么它的起点经过这个点到达终点的路径就有n种,然而一个点的起点的边不止一条,它可以不走这条边
所以你算完这个点的路径不能立马算这条边起点的路径,而是要等起点出度为零时才能算

比如
    1->2
    2->3
    1->4
    4->3

存入stack的点为3

读取stack->point=3
    边2->3     ==》2的路径+1,2的出度-1
    2的出度为0  ==》把2放进stack
    边4->3     ==》4的路径+1,4的出度-1
    4的出度为0  ==》把4放进stack
读取stack->point=2
    边1->2     ==》
        此时,2到真•终点的路径为一条=》1的路径+1,1的出度-1
    (但1还有一条边1->4没算,所以这时1的路径不能结算[1的方案=1])
    1的出度为1  ==》继续
读取stack->point=4
    边1->4     ==》同上 1的路径+1,1的出度-1
    1的出度为0  ==》把1放进stack
读取stack->point=1
    no bian
stack为空

处理完之后
怎么算总的路径呢??
不难发现,入度为0的点都是真•起点
于是我们只要统计所有真•起点的路径数之和即可

方法二:DP

其实和TOP没啥区别…

我该怎么说呢...
TOP是反向遍历
就是上面例子里,从终点开始向起点跑
但是这个DP很不讲道理,它从起点开始,把所有边跑一遍,加的是跑终点的返回值就是
    res[s]+=res[e] (e为以s为起点的边的终点)
然后这个地方它就是把res[e]记了一下,下次跑e点的时候就不需要计算,直接返回res[e]即可
思想很简单
值得一提的是,这个方法搜索深度有点大,要扩栈(就是加大搜索深度的上限)

放代码

方法一

#include <iostream>
#include <cstdio>
#include <stack>
using namespace std;

int line[123456],all=0,star[234567],nxt[234567],ent[234567],IN[123456],OUT[123456];
int n,m,res=0;

void add(int s,int e)
{
    OUT[s]++;
    IN[e]++;
    nxt[++all]=star[s];
    star[s]=all;
    ent[all]=e;
}

void TP()
{
    stack <int> go;
    for(int i=1;i<=n;i++)if(!IN[i]&&OUT[i])go.push(i),line[i]=1;
    while(go.size())
    {
        int bian,e,s=go.top();go.pop();
        for(bian=star[s],e=ent[bian];bian;bian=nxt[bian],e=ent[bian])
        {
            IN[e]--;line[e]+=line[s];
            if(!IN[e])go.push(e);
        }
    }
    for(int i=1;i<=n;i++)if(!OUT[i])res+=line[i];
}

int main()
{
    int s,e;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d",&s,&e);
        add(e,s);
    }
    TP();
    printf("%d",res);
}

方法二

#include <iostream>
#include <cstdio>
using namespace std;

const int main_stack=16;   
char my_stack[128<<20];   
int n,m,res=0,line[123456];
int all=0,star[234567],ent[234567],nxt[234567],IN[123456],OUT[123456];

void add(int s,int e)
{
    nxt[++all]=star[s];
    star[s]=all;
    ent[all]=e;
    OUT[s]++;
    IN[e]++;
}

int DP(int s)
{
    if(!OUT[s]){line[s]=1;return 1;}
    if(line[s])return line[s];
    int bian,e,add=0;
    for(bian=star[s],e=ent[bian];bian;bian=nxt[bian],e=ent[bian])
        add+=DP(e);
    line[s]=add;
    return add;
}
void GO()
{
    int s,e;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++)
        scanf("%d%d",&s,&e),add(s,e);
    for(int i=1;i<=n;i++)
    if(!IN[i]&&OUT[i])res+=DP(i);
    cout<<res;
}

int main() {   
    __asm__("movl %%esp, (%%eax);\n"::"a"(my_stack):"memory");   
    __asm__("movl %%eax, %%esp;\n"::"a"(my_stack+sizeof(my_stack)-main_stack):"%esp");   
    GO();
    __asm__("movl (%%eax), %%esp;\n"::"a"(my_stack):"%esp");
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值