BZOJ4289 [PA2012]Tax
Link
很容易想到要拆边,先把无向边拆成有向边,再把每条有向边视作新图中的点。
对于原图中的两条有向边\(e_1:a\rightarrow b,e_2:b\rightarrow c\),我们在新图中连有向边\(e_1\rightarrow e_2\),边权为\(\max(w(e_1),w(e_2))\)。
然后从\(s\)到原图上所有从\(1\)出发的有向边连边,从原图上所有到\(n\)的点到\(t\)连边,边权都为\(0\)。
这样原图中\(1\rightarrow n\)的最短路就是新图中\(s\rightarrow t\)的最短路。
直接建边会被菊花图卡成\(O(m^2)\)。
建边时枚举原图中的中转点\(b\)然后差分优化建图可以做到\(O(m)\)。
#include<cstdio>
#include<cctype>
#include<cstring>
#include<algorithm>
namespace IO
{
char ibuf[(1<<21)+1],*iS,*iT;
char Get(){return (iS==iT? (iT=(iS=ibuf)+fread(ibuf,1,(1<<21)+1,stdin),(iS==iT? EOF:*iS++)):*iS++);}
int read(){int x=0,c=Get();while(!isdigit(c))c=Get();while(isdigit(c))x=x*10+c-48,c=Get();return x;}
}
using IO::read;
using i64=long long;
const int N=500007,M=1500007;
int tot,head[N],ver[M],next[M],edge[M],vis[N];i64 dis[N];
struct pi{int x;i64 w;}a[N];
int operator<(pi a,pi b){return a.w>b.w;}
void add(int u,int v,int w){ver[++tot]=v,next[tot]=head[u],edge[tot]=w,head[u]=tot;}
int main()
{
int n=read(),m=read(),c,s,t,d;memset(dis+n+1,0x7f,m<<4);
for(int i=1,u,v,w;i<=m;++i) u=read(),v=read(),w=read(),add(u,v,w),add(v,u,w);
for(int i=tot;i;i-=2) add(i+n-1,i+n,edge[i]),add(i+n,i+n-1,edge[i]);
for(int u=1;u<=n;++u)
{
c=0;
for(int i=head[u];i;i=next[i]) a[++c]={i+n,edge[i]};
std::sort(a+1,a+c+1);
for(int i=1;i<c;++i) add(a[i].x,a[i+1].x,0),add(a[i+1].x,a[i].x,a[i].w-a[i+1].w);
if(u==1) s=a[c].x,d=a[c].w;
if(u==n) t=a[c].x;
}
a[c=1]={s,dis[s]=d};
for(int u;c;)
{
u=a[1].x,std::pop_heap(a+1,a+c+1),--c;if(vis[u])continue;vis[u]=1;
for(int i=head[u],v;i;i=next[i]) if(dis[v=ver[i]]>dis[u]+edge[i]) a[++c]={v,dis[v]=dis[u]+edge[i]},std::push_heap(a+1,a+c+1);
}
printf("%lld",dis[t]);
}