兩遍topo排序
阿新 • • 發佈:2022-03-09
目錄
題目描述
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 = 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;
}