*题解:P7831 [CCO 2021] Travelling Merchant

题目链接

解析

首先无解的点可以通过持续删出度为 \(0\) 的点来去除掉。

考虑 DP。让点代表状态,设 \(f_{i}\) 表示从点 \(i\) 出发的答案。让边代表转移,对于从 \(i\) 连向 \(j\) 的边 \(e\),有转移 \(f_{i} \leftarrow \min(f_{i},\max(f_j - p_e,r_e))\)。于是可以发现,初始答案上界为 \(\max r\)。若 \(r\) 最大的边是从 \(u\) 出发连向 \(v\) 的边 \(e\),则转移时 \(\max(f_v - p_e,r_e)\) 恰好能取到这个上界,我们也就不需要考虑 \(f_v\) 对转移造成的影响了,可以把这条边删掉。当 \(u\) 的入度为 \(0\),此时 \(f_u\) 的值已经确定,故可以去转移连接 \(u\) 的那些点,同样转移后把边删掉。一直这样转移直到不存在出度为 \(0\) 的点,然后继续取剩下的边中 \(r\) 最大的来转移并删掉,再让出度为 \(0\) 的点去转移别的点,重复此过程。

为什么跑完一轮之后取剩下边当中 \(r\) 最大的边 \(e\) 是对的?因为不存在出度为 \(0\) 的点保证了从任意一个点出发都必定能走到一个环,而由于资产单调不减,故取剩下边中的 \(\max r\) 就拥有了通过任意边的资格。

整个过程可以通过建反图拓扑排序来实现。

时间复杂度 \(O(m\log m)\)。

想到了的地方 / 考虑了的方案

找无解点的方法。

二分答案。

缩点。

没想到的地方

DP。

为什么没想到 / 怎么想到

发现图上有环且缩点做不了就否掉了 DP 做法。

代码

/*
最终应当是攒够钱后找个环走

对于每个点,找一个带环的路径使得到达路径上的边时,已经经过的边的 p 之和减去当前 r 的最小值最大 
*/
#include <bits/stdc++.h>
#define ls(x) ((x) << 1)
#define rs(x) (((x) << 1) | 1)
using namespace std;
typedef long long ll;
typedef pair<ll,int> pii;
const int N = 2e5 + 5,M = 20 + 5,mod1 = 998244353,mod2 = 1e9 + 7,base1 = 131,base2 = 13331;
struct Edge{
	int a,b,r,p,id;
	friend bool operator > (Edge e1,Edge e2){
		return e1.r > e2.r;
	}
}e[N];
ll f[N];
int ind[N],rev[N];
bool vis[N];
vector<int> g[N],g2[N];
int n,m;
int main(){
	ios::sync_with_stdio(false);
	cin.tie(0);
//	freopen("in.txt","r",stdin);
//	freopen("out.txt","w",stdout);
	memset(f,127,sizeof(f));
	cin>>n>>m;
	for(int i=1;i<=m;i++){
		cin>>e[i].a>>e[i].b>>e[i].r>>e[i].p;
		e[i].id = i;
		ind[e[i].a]++;
		g[e[i].b].push_back(i);
		g2[e[i].a].push_back(e[i].b);
	}
	sort(e + 1,e + m + 1,greater<Edge>());
	for(int i=1;i<=m;i++){
		rev[e[i].id] = i;
	}
	for(int i=1;i<=n;i++){
		for(int &j : g[i]){
			j = rev[j];
		}
	} 
	queue<int> q;
	for(int i=1;i<=n;i++){
		if(!ind[i]){
			q.push(i);
		}
	}
	int pos = 1;
	while(1){
		while(q.empty()){
			while(vis[pos]) pos++;
			if(pos > m) break;	
			int u = e[pos].a,r = e[pos].r;
			vis[pos] = true;
			f[u] = min(f[u],1ll * r);
			ind[u]--;
			if(!ind[u]){
				q.push(u);
			}
		}
		if(q.empty()) break;
		int u = q.front();
		q.pop();
		for(int i : g[u]){
			int v = e[i].a,r = e[i].r,p = e[i].p,id = e[i].id;
			if(vis[rev[id]]) continue;
			vis[rev[id]] = true;
			ind[v]--;
			if(f[u] < 9e18){	
				f[v] = min(f[v],max(1ll * r,f[u] - p));
			}
			if(!ind[v]){
				q.push(v);
			}
		}
	}
	for(int i=1;i<=n;i++){
		cout<<(f[i] < 9e18 ? f[i] : -1)<<" ";
	}
	return 0;
}
posted @ 2026-09-29 22:35  yutar  阅读(7)  评论(0)    收藏  举报