Gym - 101498L-spfa判负环&暴力&好题&细节-L. The Shortest Path

http://codeforces.com/gym/101498/problem/L
给定一个图,很多负边。问你求他的权值最小的路径,因为一个点可以重复走多次,(注意题意,是成负环才可以重复走,如果不是的话,那么有一条负边就可以反复刷一万遍自然无穷大了qwq)。问你最小的路径,
如果有负环就是输出负无穷大。
当时没有好的思路。后来看了一个题解,暴力松弛n+1次, 如果可以松弛这么多次,那么一定有负环,否则卡一个值(当最小边小于这个值就是负无穷大。) 。看不懂。
另一份题解是 用spfa判负环,
因为一次spfa只能判断以该起点能够到达的负环,所以为了方便,可以建立一个点,和所有的点连接起来就行了。
若有负环则是inf。否则维护dis的最小值。
卡了好久好久。。
原因① bfs版的spfa要松弛多次,所以dis或超过int。要用longlong,我没有。。
② bfs版的比dfs版的慢很多呢(一个400ms,一个2400ms)。
这里写图片描述

提示:下面的代码都是用GUN G++14提交的,用GNU G++5 就bfs那个超时了qwq,所以说还是DFS判环好些qwq


#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <vector>
#include <cstring>
#include <queue>
using namespace std;
/*裸spfa模板题。
因为有负权边,所以不能用dijkstra(即使最近才知道了堆优化-。-)
*/
const int maxn=2501;
int m;
vector<pair<int,int> >G[maxn];
void add(int a,int b,int c)
{   G[a].push_back(make_pair(b,c));

}
bool vis[maxn];
int dis[maxn];
bool ffl;
 void spfa(int k)
{     if(ffl) return ;
      vis[k]=true;
      for(int i=0;i<G[k].size();i++)
      {   int w=G[k][i].first;
          int  v=G[k][i].second;
          if(dis[k]+v<dis[w])
            {  dis[w]=dis[k]+v;
                if(!ffl&&vis[w])
                {  //printf("YES\n");
                   ffl=true;
                   break;
                }
                else
                {  spfa(w);
                }
            }
      }
    vis[k]=false;//回溯
}
int main()
{  int t;
   int n,d;
     int a,b,c;
    scanf("%d",&t);
    while(t--)
    {   for(int i=0;i<2501;i++)
          G[i].clear();
        scanf("%d%d",&m,&n);
        int ans=~0u>>2;//这个和 ox3f3f3f3f差不多
        for(int i=1;i<=n;i++)
            {scanf("%d%d%d",&a,&b,&d);
             add(a,b,d);
             ans=min(d,ans);
            }
           if(ans>0){
             printf("%d\n",ans);
             continue;
           }
         for(int i=1;i<=m;i++){
            add(0,i,0);
         }
         ffl=false;
         memset(vis,false,sizeof(vis));
         for(int i=0;i<=m;i++)
             dis[i]=0x3f3f3f3f;
         vis[0]=true;
         dis[0]=0;
         spfa(0);
          if(!ffl){
             int sum=1e9+7;
             for(int i=1;i<=m;i++){
                sum=min(sum,dis[i]);
             }
             printf("%d\n",sum);
          }
          else
                printf("-inf\n");
    }
    return 0;
}

bfs版本

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <vector>
#include <cstring>
#include <queue>
using namespace std;
/*裸spfa模板题。
因为有负权边,所以不能用dijkstra(即使最近才知道了堆优化-。-)
*/
const int maxn=2501;
typedef long long ll;
int m;
vector<pair<int,int> >G[maxn];
void add(int a,int b,int c)
{   G[a].push_back(make_pair(b,c));

}
 bool  vis[maxn];
     ll dis[maxn];
     int tim[maxn];
bool spfa(int k)
{
    memset(vis,0,sizeof(vis));
    memset(tim,0,sizeof(tim));
    memset(dis,0x3f,sizeof(dis));
    queue<int>q;
    q.push(k);
    dis[k]=0;
    tim[k]=1;
    vis[k]=true;
    while(!q.empty())
    {    int u=q.front();
          q.pop();
           vis[u]=false;
        for(int i=0;i<G[u].size();i++)
        {   int w=G[u][i].first;
            int v=G[u][i].second;
            if(v+dis[u]<dis[w])
              {  dis[w]=dis[u]+1ll*v;
                 if(!vis[w])
                    {vis[w]=true;
                     tim[w]++;
                     if(tim[w]>=(m+1)) return false;//大于等于基友负环
                     q.push(w);
                    }
              }
        }
    }
   return true;
}
int main()
{  int t;
   int n,d;
     int a,b,c;
    scanf("%d",&t);
    while(t--)
    {   for(int i=0;i<2501;i++)
          G[i].clear();
        scanf("%d%d",&m,&n);
        int ans=1e9+7;
        for(int i=1;i<=n;i++)
            {scanf("%d%d%d",&a,&b,&d);
             add(a,b,d);
             ans=min(ans,d);
            }
            if(ans>0){
                printf("%d\n",ans);
                continue;
            }
         for(int i=1;i<=m;i++){
            add(0,i,0);
         }
          if(spfa(0)){
             ll sum=1ll*ans;
             for(int i=1;i<=m;i++){
                sum=min(sum,dis[i]);
             }
             printf("%lld\n",sum);
          }
          else
                printf("-inf\n");



    }


    return 0;
}

暴力版本。大佬的代码

#include<bits/stdc++.h>
using namespace std;
const int inf=2000000009;
int kraw[5001][3];
int odl[2001];
int main()
{
    int t;
    scanf("%d", &t);
    while(t--)
    {
        int n,m;
        scanf("%d%d", &n, &m);
        int wyn=1000000009;
        for(int i=1; i<=n; i++)
            odl[i]=0;
        for(int i=1; i<=m; i++)
        {
            int a,b,c;
            scanf("%d%d%d", &a, &b, &c);
            kraw[i][0]=a;
            kraw[i][1]=b;
            kraw[i][2]=c;
            wyn=min(wyn,c);
        }
        int czy=1;
        int ile=0;
        while(czy==1 && wyn>-inf && ile<=n)
        {
            ile++;
            czy=0;
            for(int i=1; i<=m; i++)
                if(odl[kraw[i][1]]>odl[kraw[i][0]]+kraw[i][2])
                {
                    odl[kraw[i][1]]=odl[kraw[i][0]]+kraw[i][2];
                    wyn=min(wyn, odl[kraw[i][1]]);
                    czy=1;
                }
        }
        //cout<<wyn<<" "<<ile<<endl;
        if(ile>n || wyn<=-inf)
            printf("-inf\n");
        else
            printf("%d\n", wyn);
    }
}

2017.11.23:更新:我今天用负环的板子,又试了一下。这道题维护 边最小值,如果为正 输出正那个 不能舍去。wa了一发
原因如下:
① 如果最开始就有负边,可以确定的是,答案肯定是负边。
如果只有正边的话,我们这样见图就得不到正常的结果。
为何
这里写图片描述
如果绿边的结果(松弛的结果)为正数的话,那么 建图的 红边会再次松弛,使的最后结果不会出现正值。最大为0.(左下那个是超级源点,要和每个点建个边,这样就不用每个点都用一次spfa了)
如果松弛的 最佳结果(即答案) 为 负数的话,则不用担心这个问题。
所以。如果换一个题,求全局最短路的话(要保证为正值)。我们可以用这个建图方式。
而这个是 全局最短路(可为负),我们就要提前处理一下全是正边的情况,就是因为 前面我说得情况呢。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值