[题解]P9981 [USACO23DEC] Minimum Longest Trip G

P9981 [USACO23DEC] Minimum Longest Trip G

给定 \(n\) 个节点 \(m\) 条边的 DAG,每条边长度为 \(1\),另有一个边权。
求每个点出发的最长路,以及最长路中边权序列字典序最小的路径的边权之和。

给出的图是 DAG,第一问可以直接拓扑排序求最长路。

再考虑第二问,很暴力的想法是将每个点出发的最优序列存进 vector,再比较字典序,这样是 \(O(n^3)\) 的,不够优秀。

我们记第一问的答案为 \(d_u\),按 \(d\) 值分层,从小到大遍历每一层。

我们考虑遍历到 \(u\) 点时,其第二问的决策:

  • 选出 \(u\) 恰位于下一层的子节点,记为 \(v_1,\dots,v_k\)
  • 选择 \(u\) 到它们的出边中最小的一个;若有出边相同的节点 \(x,y\),则选择从 \(x,y\) 出发,最优序列更小的那个。

所以,我们只需要记录每个节点从自己出发的最优序列 在当前层的排名即可。决策时若遇到出边相同,无需比较字典序大小,只需要选取 \(v_1,\dots,v_k\) 中排名最小的那个进行转移。

计算排名是简单的,只需要按出边为第一关键字,子节点排名为第二关键字进行排序即可。

时间复杂度 \(O(n\log n)\),瓶颈在于排序。

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define eb emplace_back
using namespace std;
const int N=2e5+5,M=4e5+5;
struct Ed{int to,w;}e[M];
struct Dat{int x,y,z;};
int n,m,d[N],rk[N],idx,ans[N];
vector<Ed> G[N];
vector<int> a[N];
vector<Dat> tmp;
inline void dfs(int u){
	for(Ed i:G[u]){
		if(d[i.to]==-1) dfs(i.to);
		d[u]=max(d[u],d[i.to]);
	}
	d[u]++;
}
signed main(){
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	memset(d,-1,sizeof d);
	cin>>n>>m;
	for(int i=1,u,v,w;i<=m;i++){
		cin>>u>>v>>w;
		G[u].eb(Ed{v,w});
	}
	for(int i=1;i<=n;i++) if(d[i]==-1) dfs(i);
	for(int i=1;i<=n;i++) a[d[i]].eb(i);//按d值分层
	for(int i=1;i<n;i++){
		tmp.clear();
		for(int u:a[i]){
			int mn=INT_MAX,p=0;
			for(auto j:G[u]){
				int v=j.to,w=j.w;
				if(d[v]!=d[u]-1) continue;//层需要相邻
				if(w<mn) mn=w,p=v;
				else if(w==mn&&rk[v]<rk[p]) p=v; 
			}
			ans[u]=ans[p]+mn;
			tmp.eb(Dat{mn,rk[p],u});
		}
		sort(tmp.begin(),tmp.end(),[](Dat a,Dat b){
			return a.x==b.x?a.y<b.y:a.x<b.x;
		});
		for(Dat u:tmp) rk[u.z]=++idx;
	}
	for(int i=1;i<=n;i++) cout<<d[i]<<" "<<ans[i]<<"\n";
	return 0;
}
posted @ 2025-10-22 16:55  Sinktank  阅读(9)  评论(0)    收藏  举报
★CLICK FOR MORE INFO★ TOP-BOTTOM-THEME
Enable/Disable Transition
Copyright © 2023 ~ 2025 Sinktank - 1328312655@qq.com
Illustration from 稲葉曇『リレイアウター/Relayouter/中继输出者』,by ぬくぬくにぎりめし.