P4017 最大食物链计数

在这里插入图片描述

这道题…看到结果要对80112002取模,我就知道数量很大,可我不知道能有这么大…

首先测试数据生产者和最高级的食物链顶端可能有多个(亲测)

然后我想的是,遍历所有生产者,累加这些生产者到最高级消费者的路径数目,每次加起来就取一次模,害怕数据过大,特地开了long long 结果数据量还是超了,最开始我还以为是我算法写错了,害得我重新检查了一遍。后来我试着在每次dfs都取一下模AC了!!!!,这数据量是有多恐怖。。细思极恐。

思路:我们设dp[i]为以i为起点到达最高级食物链的路径条数

在这里插入图片描述
如图所示,dp[i]=dp[j]+dp[k]+dp[w]

我们依旧使用dfs的形式,其实这个dfs展开就是逆拓扑排序

dfs(i) : 返回以i为起点,食物链顶端为终点的路径条数。

对于一个顶点i,遍历其所有的出度节点,更新dp[i]的值,也就是

for(int j=0;j<Graph[i].size();j++)
        {
            int v=Graph[i][j];
            dp[i]=(dp[i]+dfs(v));
        }

递归的终止条件是什么呢?很容易想到,当那个顶点的出度等于0的时候,就返回1,也就是遇到高级消费者的时候返回1.

下面是AC代码~

#include <iostream>
#include <cstring>
#include <vector>
using namespace std;
#define Max 5005
vector<int > Graph[Max];
typedef long long int ll;
int d[Max];
void solve(int n);
int dfs(int i); //从顶点i到达最高级消费者的路径条数
ll res=0;
int dp[Max]; //从某一点i到达最高级消费者的路径条数
int main()
{
    int n,m;
    cin>>n>>m;
    int x1,x2;
    memset(d,0, sizeof(d));
    memset(dp,0, sizeof(dp));
    for(int i=1;i<=m;i++)
    {
        cin>>x1>>x2;
        Graph[x1].push_back(x2);//x2吃掉x1
        d[x2]=1; //标记有入度的节点
    }
    solve(n);
    cout<<res%80112002<<endl;
    return 0;
}

int dfs(int i) //从顶点i到达最高级消费者的路径条数
{
    if(Graph[i].size()==0)
    {
        return 1;
    }
    if(dp[i])
    {
        return dp[i];
    }
    for(int j=0;j<Graph[i].size();j++)
    {
        int v=Graph[i][j];
        dp[i]=(dp[i]+dfs(v))%80112002; //数据量很大。。。
    }
    return dp[i];
}

void solve(int n)
{
    for(int i=1;i<=n;i++)
    {
        if(!d[i]) //入度为0的节点
        {
            res=(res+dfs(i))%80112002;
        }
    }
}


上面其实就是逆拓扑排序,下面还是来一遍正的拓扑

我们设dp[i] 是 以i为终点,生产者为起点的路径条数。

在这里插入图片描述
根据分析可得:
dp[i]=dp[j]+dp[k]+dp[w]

所以我们遍历这个图的时候,要把握住,任意一个节点的前驱节点一定是在这个节点之前访问过了。因为任意一个节点的dp值都依赖与前驱节点的dp值。

所以:我们先把入度为0的节点压入队列,顺便把入度为0的节点的dp值设置为1,也就是生产者到自己的路径为1。

每次从队列弹出一个节点j,遍历这个节点j的所有的后继节点v,将这些后继节点的入度值全部-1,d[v]–,入度值减为0(d[v]==0)的v压入队列。每次遍历这些后继节点v的时候,记得更新节点v的dp值
dp[v]=dp[v]+dp[j]
上图中dp[i],从i找到前驱节点j,k,w 比较麻烦,所以用上面这种巧妙的方式,按照拓扑排序的顺序,每次找到其后继节点v的时候就更新v的dp值,这样就可以保证在拓扑序列v之前,一定所有的前驱节点都更新了这个dp[v],所以结果一定是正确的。

下面是AC代码~

#include <iostream>
#include <cstring>
#include <vector>
#include <queue>
using namespace std;
#define Max 5005
vector<int > Graph[Max];
typedef long long int ll;
int d[Max];
void f(int n);
bool flag[Max];
ll res=0;
int dp[Max]; //dp[i]:以i为终点,生产者到达i的总的路径条数
int main()
{
    int n,m;
    cin>>n>>m;
    int x1,x2;
    memset(d,0, sizeof(d));
    memset(dp,0, sizeof(dp));
    memset(flag,false, sizeof(flag));
    for(int i=1;i<=m;i++)
    {
        cin>>x1>>x2;
        Graph[x1].push_back(x2);//x2吃掉x1
        d[x2]++; //标记有入度的节点
        flag[x1]=true;
    }
    f(n);
    cout<<res<<endl;
    return 0;
}

    void f(int n)
    {
        queue<int > que;
        for(int i=1;i<=n;i++)
        {
            if(!d[i])
            {
                que.push(i);
                dp[i]=1;
            }
        }
        while(!que.empty())
        {
            int j=que.front();
            que.pop();
            for(int i=0;i<Graph[j].size();i++)
            {
                int v=Graph[j][i];
                d[v]--;
                if(!d[v])
                {
                    que.push(v);
                }
                dp[v]=(dp[v]+dp[j])%80112002;
            }
        }
        for(int i=1;i<=n;i++)
        {
            if(!flag[i])
            {
                res=(res+dp[i])%80112002;
            }
        }
    }


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值