CF1863E Speedrun 题解

CF1863E

你在玩一个游戏,要完成 \(n\) 个任务。其中对于每个任务 \(i\),它只能在某一天的第 \(h_i\) 时刻完成。游戏每天有 \(k\) 个小时,分别编号为 \(0,1,...k-1\)

给出 \(m\) 对任务间的依赖关系,\((a_i,b_i)\) 表示 \(a_i\) 必须比 \(b_i\) 先完成。保证依赖关系不形成环。

完成任务不需要时间,也就是说可以在同一天的同一时刻先后完成多个任务。

求完成所有任务所需的最短时间。这里的时间定义为:完成最后一个任务的时刻 与 开始第一个任务的时刻 之差。

多组数据,\(T\le 10^5\)\(\sum n,m\le 2\times 10^5\)\(k\le 10^9\)

  • 如果你认为直接跑一边 \(dp\) 就可以轻松解决的话,请注意最终题目要求的值

  • 如果还不理解,可以手玩第 \(4\) 个样例

  • in:
    5 0 1000
    8 800 555 35 35
    ans:
    480
    
  • 因为我们可以第 \(1\) 天完成 555 800,第 \(2\) 天完成 8 35 35。最终答案为 \((1000+35)-555=480\)


  • 因为我们容易发现,对于每一条边,他时间的差值是固定的,我们把这个作为边权

  • 例如从 \(h_1=3 \to h_2=5\),那我们可以令边 \((1,2)\) 的边权为 \(2\);相反的,如果 \(h_1 = 5, h_2 = 3\),边权应为 \(k-2\),这样我们可以得到一个有边权的 \(DAG\)

  • 如果我们枚举开始的时间是 \(x\),那对于每一个 \(x\) 我们可以在原图跑一个 \(O(n)\) 的最长路,这显然是正确的

  • 我们时间无法优化的原因是枚举的 \(x\) 是跑最长路的基础,我们改变 \(x\) 就一定会改变最长路过程中的值,但这是可以避免的,我们只需要对返图跑最长路即可,这样我们就可以得到从出度为 \(0\) 的点到入度为 \(0\) 的点的最长路,我们可以记作 \(d_i\)

  • 因为我们是对反图跑最长路,因此我们最长路得到的值 \(d_i\) 是与 \(x\) 无关的,我们考虑怎么把 \(x\) 的贡献加上去

  • 对于枚举的一个 \(x\),我们把所有入度为 \(0\) 的点分为两类处理

    1. \(h_i < x\),这一类要放到第二天开始处理,因此答案为 \(k - (x - h_i) + d_i\)

    2. \(h_i \geq x\),这一类第一天就开始处理,答案为 \(h_i - x + d_i\)

  • 我们对这两种情况取 \(\max\) 得到的值即为当前 \(x\) 下的答案,我们对于所有 \(x\)\(\min\) 即可

  • 虽然直接暴力的做还是 \(O(n^2)\) 的,但仔细想想就发现这很好优化,我们把第一、二种情况的 \(-x\) 提出来,做一个前缀后缀的 \(\max\),就可以 \(O(n)\) 的快速合并答案

  • 还有原图的边权其实是为了方便理解正着跑最长路和反着跑最长路是等价的,因此我们代码的实现并不需要给图附边权,可以直接用点权 \(dp\) 求最长路,这是一点细节

  • 最终复杂度 \(O(n)\)


  • 代码献上 AwA
#include<bits/stdc++.h>
#define ll long long
#define pcn putchar('\n')
#define ckmax(a, b) (a = max(a, b))
#define ckmin(a, b) (a = min(a, b))
using namespace std;

const int maxn = 2e5;
const ll INF = (1ll << 60);

int T, n, m;
ll K, h[maxn + 50];
int din[maxn + 50], rin[maxn + 50];
struct E{int v, nx;} e[maxn + 50];
int hd[maxn + 50], cnt = 1;
queue<int> q;
ll dis[maxn + 50];
int pos[maxn + 50];
ll pre[maxn + 50], suf[maxn + 50];

void ade(int u, int v){
	e[++ cnt] = E{v, hd[u]};
	hd[u] = cnt;
}

void tuopu(){
	while(!q.empty()) q.pop();
	for(int i = 1; i <= n; ++ i)
		dis[i] = -INF;
	
	for(int i = 1; i <= n; ++ i){
		if(!rin[i]){
			q.push(i);
			dis[i] = 0;
		}
	}
	
	while(!q.empty()){
		int u = q.front();
		q.pop();
		for(int i = hd[u]; i; i = e[i].nx){
			int v = e[i].v; ll w = h[u] - h[v];
			if(w < 0) w += K;
			if(dis[v] < dis[u] + w)
				dis[v] = dis[u] + w;
			if((-- rin[v]) == 0)
				q.push(v);
		}
	}
}

void init(){
	cnt = 1;
	for(int i = 1; i <= n; ++ i){
		din[i] = rin[i] = hd[i] = 0;
		pre[i] = suf[i] = 0;
	}
}

int main(){
	
	scanf("%d", &T);
	while(T --){
		init();
		
		scanf("%d%d%lld", &n, &m, &K);
		for(int i = 1; i <= n; ++ i)
			scanf("%lld", h + i);
		
		int u, v;
		for(int i = 1; i <= m; ++ i){
			scanf("%d%d", &u, &v);
			ade(v, u); // 这里建反图 
			++ din[v]; // 对应原图入度 
			++ rin[u]; // 对应反图入度 
		}
		
		tuopu();
		
		int cntid = 0;
		for(int i = 1; i <= n; ++ i){
			if(!din[i]){
				pos[++ cntid] = i;
			}
		}
		
		sort(pos + 1, pos + cntid + 1, [&](int x, int y){
			return h[x] < h[y];
		});
		
//		puts("------------");
//		for(int i = 1; i <= n; ++ i)
//			printf("%lld ", dis[i]);
//		pcn;
//		for(int i = 1; i <= cntid; ++ i)
//			printf("%lld ", h[pos[i]]);
//		pcn;
		
		for(int i = 1; i <= cntid; ++ i)
			pre[i] = max(pre[i - 1], K + h[pos[i]] + dis[pos[i]]);
		
		for(int i = cntid; i >= 1; -- i)
			suf[i] = max(suf[i + 1], h[pos[i]] + dis[pos[i]]);
			
//		for(int i = 1; i <= cntid; ++ i)
//			printf("%lld ", pre[i]);
//		pcn;
//		for(int i = 1; i <= cntid; ++ i)
//			printf("%lld ", suf[i]);
//		pcn;
		
		ll ans = INF;
		
		for(int i = 1; i <= cntid; ++ i){
//			printf("%lld ", max(pre[i - 1], suf[i]) - h[pos[i]]);
			ckmin(ans, max(pre[i - 1], suf[i]) - h[pos[i]]);
		}
		
		printf("%lld\n", ans);
	}
	
	return 0;
}

/*
1
5 5 24
3 22 15 0 20
4 5
3 4
3 5
1 2
1 5
*/
posted @ 2024-08-10 19:12  FOX_konata  阅读(1)  评论(0编辑  收藏  举报