【洛谷6961】[NEERC2017] Journey from Petersburg to Moscow(三分+最短路)
- 给定一张\(n\)个点\(m\)条边的无向图,定义一条路径的长度为其中前\(k\)大的边权之和。
- 求从\(1\)到\(n\)的最短路。
- \(n,m\le3000\)
枚举第\(k\)大+最短路
考虑枚举第\(k\)大的权值\(v\),然后将所有边权减去\(v\)(原本小于\(v\)的设为\(0\))。
接着只需很普通地跑遍最短路,加上\(k\times v\)更新答案即可。
关于正确性,对于一条路径,设这个边权是第\(k'\)大的:
- 如果\(k'<k\),那么排名\(k'+1\sim k\)的边权都被视作\(v\)更新答案,肯定不优。
- 如果\(k'>k\),那么排名\(k+1\sim k'\)的边权\(w\)也都对答案产生了\(w-v\)的额外贡献,肯定不优。
因此每条路径确实只会在枚举到它的第\(k\)大权值时取到最优答案,正确性得证。
三分优化
实际上这个第\(k\)大权值\(v\)是可以三分的,这样一来就能做到更优的复杂度。
代码:\(O(nlognlogm)\)
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 3000
#define LL long long
#define add(x,y,z) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y,e[ee].v=z)
using namespace std;
int n,m,k,ee,lnk[N+5],ev[N+5];struct edge {int to,nxt,v;}e[2*N+5];
typedef pair<LL,int> Pr;priority_queue<Pr,vector<Pr>,greater<Pr> > q;
int vis[N+5];LL dis[N+5];I LL Dij(CI v)//最短路
{
RI i,k;for(i=1;i<=n;++i) dis[i]=1e18,vis[i]=0;q.push(make_pair(dis[1]=0,1));
W(!q.empty()) if(k=q.top().second,q.pop(),!vis[k]) for(vis[k]=1,i=lnk[k];i;i=e[i].nxt)
dis[k]+max(e[i].v-v,0)<dis[e[i].to]&&(q.push(make_pair(dis[e[i].to]=dis[k]+max(e[i].v-v,0),e[i].to)),0);//边权减v
return dis[n];
}
int main()
{
RI i,x,y,z;for(scanf("%d%d%d",&n,&m,&k),i=1;i<=m;++i) scanf("%d%d%d",&x,&y,&z),add(x,y,z),add(y,x,z),ev[i]=z;
RI l=0,r,m1,m2;sort(ev+1,ev+m+1),r=unique(ev+1,ev+m+1)-ev-1;//排序去重
#define Calc(i) (Dij(ev[i])+1LL*k*ev[i])//最短路+k*第k大
W(r-l>2) m1=l+(r-l)/3,m2=m1+(r-l)/3,Calc(m1)<Calc(m2)?r=m2:l=m1;//三分答案
LL t=1e18;for(i=l;i<=r;++i) t=min(t,Calc(i));return printf("%lld\n",t),0;
}
待到再迷茫时回头望,所有脚印会发出光芒