title: 洛谷_P4017 最大食物鏈計數
categories:
- ACM
- DP
tags: - 食物鏈
- 拓撲排序
- 記憶化遞歸
- 鄰接表
- 優先隊列
- vector
date: 2020-04-02 13:50:09
本文用了三類方法,加上鄰接表和vector的轉換,一共寫了五個方法,分別是優先隊列,記憶dfs+vector,記憶dfs+鄰接表,拓撲排序+二維數組,拓撲排序+鄰接表
題目
題目背景
你知道食物鏈嗎?Delia 生物考試的時候,數食物鏈條數的題目全都錯了,因爲她總是重複數了幾條或漏掉了幾條。於是她來就來求助你,然而你也不會啊!寫一個程序來幫幫她吧。
題目描述
給你一個食物網,你要求出這個食物網中最大食物鏈的數量。
(這裏的“最大食物鏈”,指的是生物學意義上的食物鏈,即最左端是不會捕食其他生物的生產者,最右端是不會被其他生物捕食的消費者。)
Delia 非常急,所以你只有 11 秒的時間。
由於這個結果可能過大,你只需要輸出總數模上 8011200280112002 的結果。
輸入格式
第一行,兩個正整數 n、mn、m,表示生物種類 nn 和喫與被喫的關係數 mm。
接下來 mm 行,每行兩個正整數,表示被喫的生物A和喫A的生物B。
輸出格式
一行一個整數,爲最大食物鏈數量模上 8011200280112002 的結果。
輸入輸出樣例
輸入 #1複製
5 7
1 2
1 3
2 3
3 5
2 5
4 5
3 4
輸出 #1複製
5
說明/提示
各測試點滿足以下約定:
【補充說明】
數據中不會出現環,滿足生物學的要求。(感謝 @AKEE )
算法
優先隊列
這是我第一個想到的方法,因爲這個題目的分類在DP中,想到把邊用優先隊列按照起點從小到大排序,這樣就能滿足這樣一個轉移方程
sum[a.e]+=(v[a.s]==0)?1:sum[a.s];
其中,v[i]=0表示純起點,因爲把邊按照起點排序了,所以當前邊終點的出度一定爲0,起點又分爲兩種情況,一個是沒有入度的(純起點),如果這樣,sum[終點]應該加1。如果起點是既有入度又有出度的,sum[終點]+=sum[起點]。這個過程中還要改變點的狀態,例如1->2,2標記爲終點,然後1->3,3標記爲終點,然後2->3,2標記爲中轉點,這樣統計所有終點的sum值就可以得到結果。但是現實是殘酷的,有500000個邊,我估計隊列都滿了,並且時間效率也不好,失敗的算法
代碼
#include<cstdio>
#include <algorithm>
#include<cmath>
#include<cstring>
#include<iostream>
#include<time.h>
#include<queue>
#define PP(a,b) cout<<a<<"="<<b<<endl
#define P(a) cout<<a<<endl
#define print(a) cout<<#a<<"="<<a<<endl
#define _for(i,s,e) for(int i=s;i<=e;i++)
using namespace std;
int sum[5001];
int v[5001];//0新起點,1終點,-1中轉點
struct edge{
int s,e;
};
struct cmp{
bool operator()(edge a,edge b)
{
return a.s>b.s;
}
};
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
freopen("input.txt","r",stdin);
int n,m;
priority_queue<edge ,vector<edge>, cmp> q;
cin>>n>>m;
_for(i,1,m)
{
int s,e;
cin>>s>>e;
q.push({s,e});
}
while(!q.empty())
{
edge a=q.top();
q.pop();
// sum[a.e]=(v[a.s]==0)?(sum[a.e]+1):sum[a.s];
if(v[a.s]==0)
{
sum[a.e]++;
}
else
{
sum[a.e]+=sum[a.s];
if(v[a.s]==1)
v[a.s]=-1;
}
v[a.e]=1;
}
int ans=0;
_for(i,1,n)
{
if(v[i]==1)
ans+=sum[i];
}
cout<<ans;
return 0;
}
記憶dfs
如果我們能知道以終點上一個點爲終點的食物鏈數,那麼終點的食物鏈數就是這些點食物鏈數的和。例如,知道5是其中一個終點,並且已知只有2->5,4->5,那麼以5位終點的食物鏈數等於以2爲終點的食物鏈數+以4爲終點的食物鏈數。怎麼知道以5爲終點的邊的起點呢?需要鄰接表或者vector,怎麼知道5是不是終點呢?需要一個數組標記一下和鄰接表或者vector聯合判斷
記憶dfs+鄰接表
#include<cstdio>
#include<vector>
#include <algorithm>
#include<cmath>
#include<cstring>
#include<iostream>
#include<time.h>
#include<queue>
#define PP(a,b) cout<<a<<"="<<b<<endl
#define P(a) cout<<a<<endl
#define print(a) cout<<#a<<"="<<a<<endl
#define _for(i,s,e) for(int i=s;i<=e;i++)
using namespace std;
struct Edge{
int start,next;
} edge[5000005];
int pre[5005];
int num[5005];
bool des[5005];
int dfs(int e)
{
if(!pre[e]) return 1;
if(num[e]) return num[e];
int summ=0;
for(int i=pre[e];i;i=edge[i].next)
{
summ+=dfs(edge[i].start);
summ%=80112002;
}
return num[e]=summ;
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
//freopen("input.txt","r",stdin);
int n,m;
cin>>n>>m;
_for(i,1,m)
{
int s,e;
cin>>s>>e;
edge[i].start=s;
edge[i].next=pre[e];
pre[e]=i;
des[s]=1;
}
int ans=0;
_for(i,1,n)
{
if(!des[i]&&pre[i])
{
ans+=dfs(i);
ans%=80112002;
}
}
cout<<ans;
return 0;
}
記憶dfs+vector
#include<cstdio>
#include<vector>
#include <algorithm>
#include<cmath>
#include<cstring>
#include<iostream>
#include<time.h>
#include<queue>
#define PP(a,b) cout<<a<<"="<<b<<endl
#define P(a) cout<<a<<endl
#define print(a) cout<<#a<<"="<<a<<endl
#define _for(i,s,e) for(int i=s;i<=e;i++)
using namespace std;
int num[5005];
bool des[5005];
vector<int> v[5005];
int dfs(int e)
{
if(!v[e].size()) return 1;
if(num[e]) return num[e];
int summ=0;
for(int i=0;i<v[e].size();i++)
{
summ+=dfs(v[e][i]);
summ%=80112002;
}
return num[e]=summ;
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
freopen("input.txt","r",stdin);
int n,m;
cin>>n>>m;
_for(i,1,m)
{
int s,e;
cin>>s>>e;
v[e].push_back(s);
des[s]=1;
}
int ans=0;
_for(i,1,n)
{
if(!des[i]&&v[i].size())
{
ans+=dfs(i);
ans%=80112002;
}
}
cout<<ans;
return 0;
}
拓撲排序
每次取入度爲0的點,也就是起點,然後把這個這個起點能到達的終點+當前食物鏈數目,再找入度爲0的點,繼續。直到只剩下終點統計一下就可以,這個需要已知起點找終點,可以用二維數組或者鄰接表或者vector
拓撲+二維數組
#include<cstdio>
#include<vector>
#include <algorithm>
#include<cmath>
#include<cstring>
#include<iostream>
#include<time.h>
#include<queue>
#define PP(a,b) cout<<a<<"="<<b<<endl
#define P(a) cout<<a<<endl
#define print(a) cout<<#a<<"="<<a<<endl
#define _for(i,s,e) for(int i=s;i<=e;i++)
using namespace std;
int n,m;
int map[5005][5005];
int in[5005],out[5005];
int sum[5005];
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
//freopen("input.txt","r",stdin);
cin>>n>>m;
_for(i,1,m)
{
int s,e;
cin>>s>>e;
map[s][e]=1;
out[s]++;
in[e]++;
}
queue<int> q;
_for(i,1,n)
{
if(!in[i])
{
sum[i]=1;
q.push(i);
}
}
int summ=0;
while(!q.empty())
{
int s=q.front();
q.pop();
_for(i,1,n)
{
if(map[s][i])
{
sum[i]+=sum[s];
sum[i]%=80112002;
in[i]--;
if(!in[i])
{
if(!out[i])
{
summ+=sum[i];
summ%=80112002;
continue;
}
q.push(i);
}
}
}
}
cout<<summ;
return 0;
}
拓撲+鄰接表
#include<cstdio>
#include<vector>
#include <algorithm>
#include<cmath>
#include<cstring>
#include<iostream>
#include<time.h>
#include<queue>
#define PP(a,b) cout<<a<<"="<<b<<endl
#define P(a) cout<<a<<endl
#define print(a) cout<<#a<<"="<<a<<endl
#define _for(i,s,e) for(int i=s;i<=e;i++)
using namespace std;
struct Edge{
int end,next;
} edge[5000005];
int n,m;
int pre[5005];
int in[5005],out[5005];
int sum[5005];
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
//freopen("input.txt","r",stdin);
cin>>n>>m;
_for(i,1,m)
{
int s,e;
cin>>s>>e;
edge[i].end=e;
edge[i].next=pre[s];
pre[s]=i;
out[s]++;
in[e]++;
}
queue<int> q;
_for(i,1,n)
{
if(!in[i])
{
sum[i]=1;
q.push(i);
}
}
int summ=0;
while(!q.empty())
{
int s=q.front();
q.pop();
for(int i=pre[s];i;i=edge[i].next)
{
sum[edge[i].end]+=sum[s];
sum[edge[i].end]%=80112002;
in[edge[i].end]--;
if(!in[edge[i].end])
{
if(!out[edge[i].end])
{
summ+=sum[edge[i].end];
summ%=80112002;
continue;
}
q.push(edge[i].end);
}
}
}
cout<<summ;
return 0;
}