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了)
如果松弛的 最佳结果(即答案) 为 负数的话,则不用担心这个问题。
所以。如果换一个题,求全局最短路的话(要保证为正值)。我们可以用这个建图方式。
而这个是 全局最短路(可为负),我们就要提前处理一下全是正边的情况,就是因为 前面我说得情况呢。