两遍topo排序

题目传送门

题目描述

Time Limit: 1000 ms
Memory Limit: 256 mb

小H为了完成一篇论文,一共要完成n个实验。其中第i个实验需要ai的时间去完成。

小H可以同时进行若干实验,但存在一些实验,只有当它的若干前置实验完成时,才能开始进行该实验。

同时我们认为小H在一个实验的前置实验都完成时,就能马上开始该实验。

为了让小H尽快完成论文,需要知道在最优的情况下,最后一个完成的实验什么时候完成?

小H还想知道,在保证最后一个实验尽快完成的情况下(即保证上一问的答案不变),他想知道每个实验最晚可以什么时候开始。

设第i个实验最早可能的开始时间为fi,不影响最后一个实验完成时间的最晚开始时间为gi,请你回答

img

除以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 = 1e5+10, M = 5e5+10;
const int mod = 1e9 + 7;

int n, m; 
int d[N];// 存每个节点的入度 


//存图
int h[N], e[M], ne[M], idx = 0;

int timex[N];
int a[M], b[M];  // 注意这里是M!!!一开始写成N了!
LL ans;
LL 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);
				
			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] = (LL)max(f[j], f[t] + timex[t]); // 对t指向的所有点更新 
			 
			if(!d[j]) 
			{
				q.push(j); // 若度为0,加入队列 
					
			}
		}
	}
	
	// 得到最晚完成时间 
	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] = (LL)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] =(LL) min(g[j], g[t] - timex[j]); 
			 
			if(!d[j]) 
			{
				q.push(j); // 若度为0,加入队列 
				
			}
		}
	}
}

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[..]
	
// 	printf("hhh\n");
	
	// 重新反向建图
	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("%lld\n%lld\n", ans, total);
	return 0;
}

时间复杂度

参考文章

posted @ 2022-03-09 20:43  VanHope  阅读(39)  评论(0编辑  收藏  举报