唉。没什么话说。
首先我这个傻子考场上都知道,先把mmin求出来,再用mmin那些边的节点去spfa延伸找其他点。
然而都是口胡。
看一下细节。男神先把全部的边都减去mmin,这样一来spfa的时候不用每次都增加,减少编程复杂度,然后spfa的时候也是一个我没有想到的操作,就是他非常心安理得的第二维只开了2
一个朴素的想法是f[i][j]表示走到点i,有j条边要使用后面的边权的最短路。f[i][j]可以转移到f[k][0]或f[k][j+1]。然而这样边数是O(n3)O(n3)的,spfa不资磁。。
但是考虑做最短路的目的是找到一个标记点,走到很多个中间点显然不是什么好方案。具体来说,从i走j条边到k,只是为了使用最后一条边权(s,k),那为什么不直接i->s->k呢?所以f[i][j]中的j只需要取0,1。
ORZ
#include<cstdio> #include<iostream> #include<cstring> #include<cstdlib> #include<algorithm> #include<cmath> using namespace std; typedef long long LL; const LL inf(1LL<<60); int n;LL mmin; LL mp[2100][2100]; struct list_ { int x,w; }list[4100];bool v[2100][2]; LL d[2100][2]; void spfa() { memset(d,63,sizeof(d)); memset(v,false,sizeof(v)); int head=1,tail=1; for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) if(mp[i][j]==0&&i!=j) { d[i][0]=0; v[i][0]=true; list[tail].x=i; list[tail].w=0; tail++; break; } while(head!=tail) { int x=list[head].x,w=list[head].w; for(int y=1;y<=n;y++) { if(x==y)continue; LL dis=d[x][w]+(w+1)*mp[x][y]; if(d[y][0]>dis) { d[y][0]=dis; if(v[y][0]==false) { v[y][0]=true; list[tail].x=y; list[tail].w=0; tail++;if(tail==4050)tail=1; } } if(w==0) { dis=d[x][w]; if(d[y][1]>d[x][w]) { d[y][1]=d[x][w]; if(v[y][1]==false) { v[y][1]=true; list[tail].x=y; list[tail].w=1; tail++;if(tail==4050)tail=1; } } } } v[x][w]=false; head++;if(head==4050)head=1; } } int main() { freopen("b.in","r",stdin); freopen("b.out","w",stdout); scanf("%d",&n);mmin=inf; for(int i=1;i<=n;i++) { for(int j=i+1;j<=n;j++) { scanf("%lld",&mp[i][j]); mp[j][i]=mp[i][j]; mmin=min(mmin,mp[i][j]); } } for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) if(i!=j) mp[i][j]-=mmin; spfa(); for(int i=1;i<=n;i++)printf("%lld\n",d[i][0]+(n-1)*mmin); return 0; }