【BZOJ2407】探险

题目

题目链接:https://darkbzoj.tk/problem/2407
探险家小T好高兴!X国要举办一次溶洞探险比赛,获奖者将得到丰厚奖品哦!小T虽然对奖品不感兴趣,但是这个大振名声的机会当然不能错过!
比赛即将开始,工作人员说明了这次比赛的规则:每个溶洞和其他某些溶洞有暗道相连。两个溶洞之间可能有多条道路,也有可能没有,但没有一条暗道直接从自己连到自己。参赛者需要统一从一个大溶洞出发,并再次回到这个大溶洞。
如果就这么点限制,那么问题就太简单了,可是举办方又提出了一个条件:不能经过同一条暗道两次。这个条件让大家犯难了。这该怎么办呢?
到了大溶洞口后,小T愉悦地发现这个地方他曾经来过,他还记得有哪些暗道,以及通过每条暗道的时间。小T现在向你求助,你能帮他算出至少要多少时间才能回到大溶洞吗?
(nleq 10^4,mleq 2 imes 10^5)

思路

两个做法。其中第一个做法并没有办法解决有重边且边权相同的情况。第二种做法可以解决。


先把从 (1) 开始的单元最短路径求出来,记录每一个点的最短路 (dis) 以及最短路中第一个点(不算 (1))是哪一个点 (pre)
然后考虑构造一张新图。对于原图中一条边 ((u,v,d))

  • 如果 (u=1,pre[v]=v),说明到 (v) 的最短路就是 ((1,v,d)) 这一条边。那么忽略这一条边。
  • 如果 (u=1,pre[v] eq v),说明我们可以从 (1)(v) 走这一条边并且不经过最短路上的边。连边 ((1,v,d))
  • 如果 (v=1,pre[u]=u),这条边就是最短路,连边 ((u,n+1,d))
  • 如果 (v=1,pre[u] eq u),那么可以走 (1,pre[u],u,n) 这一条路径,连边 ((1,n+1,dis[u]+d))
  • 如果 (u eq 1,v eq 1,pre[u]=pre[v]),那么就可以通过 (1)(pre[u]) 的最短路,再加上 (pre[u])(1) 的另一条路。连边 ((u,v,d))
  • 如果 (u eq 1,v eq 1,pre[u] eq pre[v]),连边 ((1,v,dis[u]+d))

然后跑新图的最短路,答案就是 (dis[n+1])
这个构造方法很巧妙的把 (1) 到任意一个点的最短路和非最短路扔到了图的两边,保证不会重复经过。但是如果遇到这种数据:

2
1 2 1 1
1 2 1 1

就会因为最短路有两条而错误。但是 bzoj 的数据并没有这样的,可以通过。
时间复杂度 (O((n+m)log n))


第二种算法则考虑到如果会走重复的路径,那么一定是 (1) 到某一个点的路走两次。
所以直接把一端是点 (1) 的路二进制分组一下,每次从一组开始往另一组跑最短路就行了。
时间复杂度 (O((n+m)log nlog m))。吸氧能过。

代码

算法一。算法二心情好就会补的。

#include <bits/stdc++.h>
#define mp make_pair
#define se second
using namespace std;
typedef long long ll;

const int N=10010,M=400010;
int n,m,tot,head[N],pre[N],U[M],V[M],D1[M],D2[M];
ll dis[N];
bool vis[N];

struct edge
{
	int next,to;
	ll dis;
}e[M];

void add(int from,int to,ll dis)
{
	e[++tot]=(edge){head[from],to,dis};
	head[from]=tot;
}	

void addedge(int x,int y,int d)
{
	if (x==1 && pre[y]!=y) add(1,y,d);
	if (y==1 && pre[x]!=x) add(1,n+1,dis[x]+d);
	if (y==1 && pre[x]==x) add(x,n+1,d);
	if (x!=1 && y!=1 && pre[x]==pre[y]) add(x,y,d);
	if (x!=1 && y!=1 && pre[x]!=pre[y]) add(1,y,dis[x]+d);
}

void dij()
{
	memset(dis,0x3f3f3f3f,sizeof(dis));
	memset(vis,0,sizeof(vis));
	priority_queue<pair<ll,int> > q;
	q.push(mp(0,1)); dis[1]=0;
	while (q.size())
	{
		int u=q.top().se; q.pop();
		if (vis[u]) continue;
		vis[u]=1;
		for (int i=head[u];~i;i=e[i].next)
		{
			int v=e[i].to;
			if (dis[v]>dis[u]+e[i].dis)
			{
				dis[v]=dis[u]+e[i].dis; pre[v]=pre[u];
				if (u==1) pre[v]=v;
				q.push(mp(-dis[v],v));
			}
		}
	}
}

int main()
{
	memset(head,-1,sizeof(head));
	scanf("%d%d",&n,&m);
	for (int i=1;i<=m;i++)
	{
		scanf("%d%d%d%d",&U[i],&V[i],&D1[i],&D2[i]);
		add(U[i],V[i],D1[i]); add(V[i],U[i],D2[i]);
	}
	dij();
	memset(head,-1,sizeof(head));
	tot=0;
	for (int i=1;i<=m;i++)
	{
		addedge(U[i],V[i],D1[i]);
		addedge(V[i],U[i],D2[i]);
	}
	dij();
	cout<<dis[n+1];
	return 0;
}
原文地址:https://www.cnblogs.com/stoorz/p/15037910.html