福慧双修&探险 BZOJ4398&BZOJ2407
分析:
双倍经验(数据范围不同)。
我们考虑,我们必定是从1走一条边到节点i,之后从i到j跑最短路,之后再从j到1走另一条边的情况下,不会重复,并且是答案。那么我们考虑预处理出pre[i]表示从1走到i满足最短路的并且经过pre[i],pre[i]为路径第二个节点。那么,针对每一个边,(x,y,z,v)满足当x!=1&&y!=1时,如果pre[x]!=pre[y]连接1,x,dis[y]+z和1,y,dis[x]+v(因为答案如果经过这个边,那么答案一定是dis[x]+dis[y]+z或者dis[x]+dis[y]+v中的一个),如果pre[x]==pre[y],那么保留这两条边。需要特判x==1||y==1的情况,具体看代码。
附上代码:
#include <cstdio> #include <algorithm> #include <cmath> #include <cstdlib> #include <cstring> #include <iostream> #include <queue> #include <vector> using namespace std; #define N 10010 struct node { int to,next,val; }e[600010]; int dis[N],pre[N],vis[N],head[N],cnt,n,m; void add(int x,int y,int z) { e[cnt].to=y; e[cnt].next=head[x]; e[cnt].val=z; head[x]=cnt++; } void spfa() { queue <int>q;q.push(1);dis[1]=0; while(!q.empty()) { int x=q.front();q.pop();vis[x]=0; for(int i=head[x];i!=-1;i=e[i].next) { int to1=e[i].to; if(dis[to1]>dis[x]+e[i].val) { dis[to1]=dis[x]+e[i].val; pre[to1]=pre[x]; if(x==1)pre[to1]=to1; if(!vis[to1])q.push(to1),vis[to1]=1; } } } //printf("%d %d %d\n",dis[1],dis[2],dis[3]); } struct no { int x,y,z,v; }a[400010]; int main() { //freopen("both.in","r",stdin);freopen("both.out","w",stdout); memset(head,-1,sizeof(head));cnt=0;memset(dis,0x3f,sizeof(dis)); scanf("%d%d",&n,&m); for(int i=1;i<=m;i++) { scanf("%d%d%d%d",&a[i].x,&a[i].y,&a[i].z,&a[i].v); add(a[i].x,a[i].y,a[i].z);add(a[i].y,a[i].x,a[i].v); } spfa(); memset(head,-1,sizeof(head));cnt=0; for(int i=1;i<=m;i++) { int x=a[i].x,y=a[i].y,z=a[i].z,v=a[i].v; if(x==1) { if(pre[y]!=y)add(1,y,z),dis[n+1]=min(dis[n+1],dis[y]+v); else add(y,n+1,v); continue; } if(y==1) { if(pre[x]!=x)add(1,x,v),dis[n+1]=min(dis[n+1],dis[x]+z); else add(x,n+1,z); continue; } if(pre[x]!=pre[y])add(1,y,dis[x]+z),add(1,x,dis[y]+v); else add(x,y,z),add(y,x,v); } for(int i=1;i<=n;i++)dis[i]=1<<30; spfa(); printf("%d\n",dis[n+1]); return 0; }