洛谷-P4017-最大食物鏈計數


title: 洛谷_P4017 最大食物鏈計數
categories:

  • ACM
  • DP
    tags:
  • 食物鏈
  • 拓撲排序
  • 記憶化遞歸
  • 鄰接表
  • 優先隊列
  • vector
    date: 2020-04-02 13:50:09

本文用了三類方法,加上鄰接表和vector的轉換,一共寫了五個方法,分別是優先隊列,記憶dfs+vector,記憶dfs+鄰接表,拓撲排序+二維數組,拓撲排序+鄰接表

題目

題目背景

你知道食物鏈嗎?Delia 生物考試的時候,數食物鏈條數的題目全都錯了,因爲她總是重複數了幾條或漏掉了幾條。於是她來就來求助你,然而你也不會啊!寫一個程序來幫幫她吧。

題目描述

給你一個食物網,你要求出這個食物網中最大食物鏈的數量。

(這裏的“最大食物鏈”,指的是生物學意義上的食物鏈,即最左端是不會捕食其他生物的生產者,最右端是不會被其他生物捕食的消費者。)

Delia 非常急,所以你只有 11 秒的時間。

由於這個結果可能過大,你只需要輸出總數模上 8011200280112002 的結果。

輸入格式

第一行,兩個正整數 n、mnm,表示生物種類 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

說明/提示

各測試點滿足以下約定:

img

【補充說明】

數據中不會出現環,滿足生物學的要求。(感謝 @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;
} 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章