1. 程式人生 > 其它 >兩遍topo排序

兩遍topo排序

目錄

題目傳送門

題目描述

Time Limit: 1000 ms
Memory Limit: 256 mb

小H為了完成一篇論文,一共要完成n個實驗。其中第i個實驗需要ai的時間去完成。

小H可以同時進行若干實驗,但存在一些實驗,只有當它的若干前置實驗完成時,才能開始進行該實驗。

同時我們認為小H在一個實驗的前置實驗都完成時,就能馬上開始該實驗。

為了讓小H儘快完成論文,需要知道在最優的情況下,最後一個完成的實驗什麼時候完成?

小H還想知道,在保證最後一個實驗儘快完成的情況下(即保證上一問的答案不變),他想知道每個實驗最晚可以什麼時候開始。

設第i個實驗最早可能的開始時間為fi,不影響最後一個實驗完成時間的最晚開始時間為gi,請你回答

除以10^9+7所得的餘數。

題目保證有解。

輸入輸出格式

輸入描述:

從標準輸入讀入資料。
第一行輸入一個整數n,m。
第二行輸入n個正整數,a1,a2,.....an,描述每個實驗完成所需要的時間。
接下來讀入m行,每行讀入兩個整數u,v,表示編號為u的實驗是編號為v的實驗的前置實驗。
對於所有的輸入資料,都滿足1<=n<=10^5,1<=m<=5*10^5,1<=ai<=10^6。

輸出描述:

輸出到標準輸出。
第一行輸出一個整數表示最晚完成的實驗的時間。
第二行輸出一個整數表示除以10^9+7所得的餘數。

輸入輸出樣例

輸入樣例#:

複製

7 5
11 20 17 10 11 17 17
5 4
6 1
7 3
2 4
2 1

輸出樣例#:

複製

3 4
7840

提示

第一個點最早開始時間為20,最晚開始時間為23。
第二個點最早開始時間為0,最晚開始時間為3。
第三個點最早開始時間為17,最晚開始時間為17。
第四個點最早開始時間為20,最晚開始時間為24。
第五個點最早開始時間為0,最晚開始時間為13。
第六個點最早開始時間為0,最晚開始時間為6。
第七個點最早開始時間為0,最晚開始時間為0。

題目來源

清華大學2019年機試題

兩遍topo排序

分析

把給的例子畫圖畫出來,然後分析

step1

每個實驗u的最早開始時間

  • 如果這個實驗沒有前置實驗,那麼其最早開始時間就是0:f[u] = 0
  • 如果其有k個前置實驗,那麼這個實驗最早的開始時間是其前k個前置實驗完成的最完時間: f[u] = min(f[u], f[vi] + time[vi]);(竟然也是一個動態規劃?!)

step2

然後求最後一個實驗的最優完成時間

  • 做完上面的topo之後,可以得到每個實驗的f[i],所有實驗儘早開始,那麼最後一個實驗的最優完成時間就應該是:所以實驗開始加上實驗時間的最大值:ans = max(ans, f[i] + time[i])

step3

下一步求在保證上一步ans不變的情況下,每個實驗的最晚開始時間,這一步也是比較難的,主要是思考

我們反過來思考,現在知道了每個實驗花費的時間,和所有實驗的最晚完成時間,我們只需要求出每個實驗最晚的完成時間,然後減去該實驗所花費的時間,就是該實驗的最晚開始時間

  • 首先反向建圖
  • 對於初始度為0的點,說明這些都是最後完成的實驗,其最晚完成時間都可以是上一步求的ans,從而推出其最晚開始時間就是 g[i] = ans - time[i]
  • 對於其他點u
    • 假設有k個點指向u,也已經知道了這k個點的最晚開始時間g[vi],那麼u點的最晚完成時間應該是所有k個點最晚開始時間的最大值!!!所以 g[u] = min(g[vi]) - time[u] = min(g[u], g[vi] - time[u])

注意:

  • 最後結果很大,用long long型別!不然只能過66.6%
  • 第二次topo之前,需要先將原來的圖陣列、其他用到的陣列初始化;同時要對g陣列初始化成INF

程式碼

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
typedef long long LL;
const int N = 100010, M = 2*N;
const int mod = 1e9 + 7;

int n, m; 
int d[N];// 存每個節點的入度 

int res[N], cnt = 0;
//存圖
int h[N], e[M], ne[M], idx = 0;

int timex[N];
int a[N], b[N]; 
int ans;
int f[N], g[N];


void add(int a, int b)
{
	e[idx] = b;
	ne[idx] = h[a];
	h[a] = idx++;
}

void topo()
{
	queue<int> q; //存所有入度為0的點 
	// 先把入度為0的點加入隊中 
	for(int i = 1; i <= n; i++)
		if(!d[i])
		{
			q.push(i);
			res[cnt++] = i;		
			f[i] = 0; // 最早開始時間 
		}
	
	while(q.size())
	{
		int t = q.front();
		q.pop();
		
		// t所有指向的點 
		for(int i = h[t]; i != -1; i = ne[i])
		{
			int j = e[i];
			
			d[j]--; // 該點入度-1 
			f[j] = max(f[j], f[t] + timex[t]); // 對t指向的所有點更新 
			 
			if(!d[j]) 
			{
				q.push(j); // 若度為0,加入佇列 
				res[cnt++] = j;	
			}
		}
	}
	
	// 得到最晚完成時間 
	for(int i = 1; i <= n; i++) ans = max(ans, f[i] + timex[i]); 
}

void topo2()
{
	memset(g, 0x3f, sizeof g);
	
	queue<int> q; //存所有入度為0的點 
	// 先把入度為0的點加入隊中 
	for(int i = 1; i <= n; i++)
		if(!d[i])
		{
			q.push(i);
			g[i] = ans - timex[i]; // 最晚開始時間 
		}
	
	while(q.size())
	{
		int t = q.front();
		q.pop();
		
		// t所有指向的點 
		for(int i = h[t]; i != -1; i = ne[i])
		{
			int j = e[i];
			
			d[j]--; // 該點入度-1 
			g[j] = min(g[j], g[t] - timex[j]); 
			 
			if(!d[j]) 
			{
				q.push(j); // 若度為0,加入佇列 
				res[cnt++] = j;	
			}
		}
	}
}

void init()
{
	memset(h, -1, sizeof h);
	memset(e, 0, sizeof e);
	memset(ne, 0, sizeof ne);
	idx = 0;
	memset(d, 0, sizeof d);
}

int main()
{
	
	init();
	
	scanf("%d%d", &n, &m);
	for(int i = 1; i <= n; i++) scanf("%d", &timex[i]);
	
	for(int i = 0; i < m; i++)
	{
		scanf("%d%d", &a[i], &b[i]);
		add(a[i], b[i]); // 正向建邊
		d[b[i]]++; //入度+1 
	}
	
	topo(); // 得到ans,f[..]
	
	// 重新反向建圖
	init();
	
	for(int i = 0; i < m; i++)
	{
		add(b[i], a[i]);
		d[a[i]]++;	
	} 
	topo2(); // 得到 g[..]
	
	
//	for(int i = 1; i <= n; i++)
//		printf("%d %d\n", f[i], g[i]);
	
	//
	LL total = 1;
	for(int i = 1; i <= n; i++)
		total = (total % mod) * ((LL)g[i] - f[i] + 1 % mod) % mod;
	 
	printf("%d\n%lld\n", ans, total);
	return 0;
}

時間複雜度

參考文章