zoukankan      html  css  js  c++  java
  • 【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;
    }
    
  • 相关阅读:
    大学生创业不可或缺的六项品质
    C#的9*9乘法表!
    湖北武汉的进来!看看!
    每束焰火都装了电脑芯片
    学习C#之旅 魔泡排序
    学习C#之旅(C#语言基础,运算符)
    主攻ASP.NET.3.5.MVC架构之重生: URL Routing (三)
    主攻ASP.NET.3.5.MVC架构之重生: LINQ(六)
    【HDU】3341 Lost's revenge
    【HDU】2243 考研路茫茫――单词情结
  • 原文地址:https://www.cnblogs.com/stoorz/p/15037910.html
Copyright © 2011-2022 走看看