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)}\) .
代码还没写

浙公网安备 33010602011771号