两遍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])
- 假设有k个点指向u,也已经知道了这k个点的最晚开始时间
注意:
- 最后结果很大,用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;
}