福慧双修&探险 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
",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
",dis[n+1]);
	return 0;
}

  

原文地址:https://www.cnblogs.com/Winniechen/p/9107877.html