P9981 [USACO23DEC] Minimum Longest Trip G

P9981 [USACO23DEC] Minimum Longest Trip G

模拟赛T3考到了死在了法一

法一

首先对于不考虑边权的最长路,可以按照拓扑序 \(dp\),这是容易的。
然后考虑第二问怎么做。
设已经得到的结点 第一问(最长路长度)答案为 \(g_i\)。
我们考虑这个拓扑序 \(dp\) 的过程,其本质就是在结点 的各个出边中,寻找最优的答案。
考虑按最长路从小到大处理每个层级,最小层级没有出边,显然字典序排名相同。
接下来考虑推导出下一层。
具体的,对于 \((u,v)\),枚举出边 ,按照 \((u,v)\) 的边权为第一关键字, 在上一层(最长路短
的那一层)的排名为第二关键字,找到 \(u\) 的最佳转移。
就是先要求 \((u,v)\) 的边权最小,再要求 \(v\) 的排名最小。
这一级都完成后,再按照同样的双关键字方法,把这一层的点排序,就得到当前层的排名。
时间复杂度 \(\mathcal{O(n \log n)}\)。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll INF=1e18;
const int N=2e5+5;
const int M=4e5+5;
struct node{
	int to,nxt;ll w;
}edge[M];
int tot,head[N],in[N],n,m,g[N];
ll f[N];
void add(int u,int v,ll w){
	edge[++tot].to=v;
	edge[tot].nxt=head[u];
	edge[tot].w=w;
	head[u]=tot;
}
node _edge[M];
int _tot,_head[N],_in[N];
void add1(int u,int v,ll w){
	_edge[++_tot].to=v;
	_edge[_tot].nxt=_head[u];
	_edge[_tot].w=w;
	_head[u]=_tot;
}
inline ll read(){
	int x=0,t=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')t=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return x*t;
}
struct res{
	ll w;int to,rnk;
	bool operator<(const res& b) const{
		if(w!=b.w)return w<b.w;
		return rnk>b.rnk;
	}
};
int rk[N],idx[N],cnt;
bool cmp(int a,int b){
	return g[a]<g[b];
}
void solve(){
	queue<int> q;
	for(int i=1;i<=n;i++){
		if(!in[i])q.push(i);
	}
	while(!q.empty()){
		int u=q.front();q.pop();
		for(int i=head[u];i;i=edge[i].nxt){
			int v=edge[i].to;ll w=edge[i].w;
			in[v]--;
			if(g[u]+1>g[v]){
				g[v]=g[u]+1;
			}
			if(!in[v])q.push(v);
		}
	}
	for(int i=1;i<=n;i++)idx[i]=i;
	sort(idx+1,idx+n+1,cmp);
	priority_queue<res> Q;
	int mxg=0;
	for(int i=1;i<=n;i++){
		int u=idx[i];
		if(g[u]!=mxg){
			mxg=g[u];
			while(!Q.empty()){
				res tmp=Q.top();Q.pop();
				rk[tmp.to]=++cnt;
			}
		}
		ll minw=INF;int mxrk=0;
		if(g[u]){
			for(int p=_head[u];p;p=_edge[p].nxt){//寻找最佳转移
				int v=_edge[p].to;ll w=_edge[p].w;
				if(g[v]==g[u]-1)minw=min(minw,w);
			}
			for(int p=_head[u];p;p=_edge[p].nxt){
				int v=_edge[p].to;ll w=_edge[p].w;
				if(g[v]==g[u]-1&&w==minw){mxrk=max(mxrk,rk[v]);}
			}
			for(int p=_head[u];p;p=_edge[p].nxt){
				int v=_edge[p].to;ll w=_edge[p].w;
				if(g[v]==g[u]-1&&w==minw&&mxrk==rk[v]){
					f[u]=f[v]+w;
					Q.push({w,u,rk[v]});
					break;
				}
			}
		}
		
	}
}

int main(){
//	freopen("journey.in","r",stdin);
//	freopen("journey.out","w",stdout);
	n=read();m=read();
	for(int i=1;i<=m;i++){
		int u=read(),v=read();ll w=read();
		in[u]++;
		add(v,u,w);add1(u,v,w);
	}
	solve();
	for(int i=1;i<=n;i++){
		printf("%d %lld\n",g[i],f[i]);
	}
	return 0;
}

法二

考虑第二问,发现是在一群链上面找字典序最小的,暴力就是逐个跳,考虑优化这个暴力,发现其实前面相同的数都是没用的.因为我只需要找到第一个小于的位置就能知道那个是字典序更小的,所以这里我们可以使用倍增+哈希的方法来解决这个问题.
具体地,这里引用一下洛谷题解区大佬的话

类似于字符串哈希的方法,可以预处理前缀哈希使得单次查询一个子串的复杂度变成 \(\mathcal{O(1)}\)。但是因为没有记录一条链上的所有节点所以不便于二分,考虑换成倍增。
然后是类似于最近公共祖先的倍增写法,不断的向上跳找到第一个标签值不同的位置,然后比较即可。

时间复杂度也是 \(\mathcal{O(n \log n)}\) .

代码还没写

posted @ 2026-09-07 20:30  dgcsjfs  阅读(13)  评论(2)    收藏  举报