【BZOJ2407】探险

题目

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

思路

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


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

  • 如果 \(u=1,pre[v]=v\),说明到 \(v\) 的最短路就是 \((1,v,d)\) 这一条边。那么忽略这一条边。
  • 如果 \(u=1,pre[v]\neq v\),说明我们可以从 \(1\) 到 \(v\) 走这一条边并且不经过最短路上的边。连边 \((1,v,d)\)。
  • 如果 \(v=1,pre[u]=u\),这条边就是最短路,连边 \((u,n+1,d)\)。
  • 如果 \(v=1,pre[u]\neq u\),那么可以走 \(1,pre[u],u,n\) 这一条路径,连边 \((1,n+1,dis[u]+d)\)。
  • 如果 \(u\neq 1,v\neq 1,pre[u]=pre[v]\),那么就可以通过 \(1\) 到 \(pre[u]\) 的最短路,再加上 \(pre[u]\) 到 \(1\) 的另一条路。连边 \((u,v,d)\)。
  • 如果 \(u\neq 1,v\neq 1,pre[u]\neq 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 n\log 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;
}
上一篇:《数学分析》笔记:实数集和函数 3


下一篇:扩展:Flash消息