负环判断

原题: https://www.luogu.org/problemnew/show/P3385

题意:

n个点,m条边,判断是否存在负环。(输出YE5不是YES,N0不是NO)

解析:

这个题目真的是累,网上也没多少正确的题解,说的一些我都懂,但是时间复杂度不优秀。在洛谷和csdn上找了好久。真的是,洛谷时间复杂度前几的只有两种算法。第一种:百来行的建树做的,很优秀but看不懂;第二种:special judge,嗯,也很优秀。

注意细节: 题目要求环必须带上点1,所以初始时只有dis[1]=0,其他点设为inf。这样做松弛操作必须从点1开始。如果不要求带上某个点,则全部赋值为0。


dfs就是往dis更优的方向搜,如果搜会到起点就是一个环了。计算了复杂度,不行啊。

TLE dfs

#include<bits/stdc++.h>
using namespace std;

const int N=2010;
const int M=3010;

int n,m;

int head[N],nex[2*M],to[2*M],val[2*M],now;
void init(){
    now=0;
    memset(head,-1,sizeof head);
}
void add(int a,int b,int v){
    nex[++now]=head[a],head[a]=now,to[now]=b,val[now]=v;
}

int dis[N];
bool vis[N];
bool flag;

void spfa(int p){
    if(vis[p]){
        flag=1;
        return;
    }
    vis[p]=1;
    for(int i=head[p];~i;i=nex[i]){
        int u=to[i];
        if(dis[u]>dis[p]+val[i]){
            dis[u]=dis[p]+val[i];
            if(u==1){flag=1;return;}
            if(flag)return;
            spfa(u);
            if(flag)return;
        }
    }
    vis[p]=0;
}

bool HaveNeg(){
    memset(dis,0x3f,sizeof(dis));
    dis[1]=0;
    memset(vis,0,sizeof(vis));
    flag=0;
    spfa(1);
    return flag;
}

int main(){
    int t;scanf("%d",&t);
    while(t--){
        init();
        scanf("%d%d",&n,&m);
        for(int i=1;i<=m;i++){
            int a,b,v;scanf("%d%d%d",&a,&b,&v);
            add(a,b,v);
            if(v>=0)add(b,a,v);
        }
        if(HaveNeg())printf("YE5\n");
        else printf("N0\n");
    }
}


650ms 最常规spfa+bfs

常规思路,如果有一个负环,那么这个负环上的点的dis会被一直刷的更低。因为一个点正常情况下(无负环)一条持续更新的链顶多串N个点。那么当串N+1个点时,说明有负环(在更新别人后又更新回来了)。

主要来讲一讲这种写法的细节。

可以看到,首先只将1入队,dis[1]=0,其他为inf,那么效果怎么样呢?
在这里插入图片描述
起初我以为可以检测出带有1节点的负环,但是很明显,第三组案例不是。再看一下算法,每次取队列中的点,把后面的点拉进来,一进一出之时num数组慢慢增加。

所以算法正确性的前提为:每个联通块必须有一个点进队列,并且将此点的初始dis设为0。

#include<iostream>
#include<queue>
#include<cstring>
using namespace std;
const int maxn=198391;
struct Edge{
    int u,v,w,nxt;
}edge[maxn<<1];
int fst[maxn],cnt;
inline void addedge(int u,int v,int w){
    edge[++cnt].u=u,edge[cnt].v=v,edge[cnt].w=w,
    edge[cnt].nxt=fst[u],fst[u]=cnt;
}
int N,M;
int dis[maxn];int ins[maxn];
int spfa(){
    memset(dis,0x3f,sizeof(dis));
    memset(ins,0,sizeof(ins));
    queue<int>q;
    q.push(1);
    int num[maxn];
    ins[1]=1;
    num[1]=1;
    dis[1]=0;
    while(!q.empty()){
        int u=q.front();q.pop();
        ins[u]=0;
        for(int i=fst[u];i;i=edge[i].nxt){
            int v=edge[i].v;
            if(dis[v]>dis[u]+edge[i].w){
                dis[v]=dis[u]+edge[i].w;
                num[v]=num[u]+1;
                if(num[v]>N)return 1;
                if(!ins[v]){
                    ins[v]=1;
                    q.push(v);
                }
            }
        }
    }
    return 0;
} 

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    int T;cin>>T;
    while(T--){int x,y,ww;
        cin>>N>>M;
        while(M--){
            cin>>x>>y>>ww;
            addedge(x,y,ww);
            if(ww>=0)addedge(y,x,ww);
        }
        if(spfa())cout<<"YE5\n";
        else cout<<"N0\n";
        for(int i=1;i<=N;i++)fst[i]=0;
    }
}

扒到一个看起来较舒服的写法,直接做n次再判断。

400ms bellam-ford加优化

这种算法得出的结果同上,算法正确性的前提为:每个联通块必须有一个点初始dis设为0。

这个算法,如果不要求某个点可以到达此负环,dis直接全为0即可。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

const int inf = 0x3f3f3f3f;
const int N = 2010;
const int M = 3010;

int a[M], b[M], c[M];
int dis[N];

void relax(int a, int b, int c) {
    if(dis[a] != inf)
        dis[b] = min(dis[b], dis[a] + c);
}
bool test1(int a, int b, int c) {
    return (dis[b] > dis[a] + c);
}
bool test(int a, int b, int c) {
    if(dis[a] == inf)
        return 0;
    if(c < 0)
        return test1(a, b, c);
    return test1(a, b, c) || test1(b, a, c);
}
bool solve() { 
    int n, m;
    scanf("%d%d", &n, &m);
    memset(dis, 0x3f, sizeof(int) * (n + 2));
    dis[1] = 0;
    for(int i = 1; i <= m; i++)
        scanf("%d%d%d", &a[i], &b[i], &c[i]);
    for(int r = 1; r < n; r++)
        for(int i = 1; i <= m; i++) {
            relax(a[i], b[i], c[i]);
            if(c[i] >= 0)
                relax(b[i], a[i], c[i]);
        }
    for(int i = 1; i <= m; i++)
        if(test(a[i], b[i], c[i])) {
            return 1;
        }
    return 0;
}
int main() {
    int t;
    cin >> t;
    while(t--)
        if(solve())//有负环
            printf("YE5\n");
        else
            printf("N0\n");
    return 0;
}


看不懂级别

68ms 神仙代码

#pragma GCC optimize(3)//手动Ox优化
#include<cstdio>
#include<cstring>
#include<queue>
#include<vector>
using namespace std;
/*
    思路 :免得打到一半忘记了
     1.建树 u->v 能松弛就建边 建树带上深度
     2.删除不是最优的入队节点(该节点代表的树,该节点标记要加上) 新的节点由该节点重新延申
     3.判断负环是看该树以下有么有该树的根节点 存在的话出现负环    ?????
     4.开始 码 代码!
*/
const int N=2050,M=6050;
struct side   // 边
{
    int nxt,to,d;
} e[M];
struct tree  // 树
{
    int c,fa,dep;  // 标记,父亲,深度
    vector < int > son;  // 儿子
}f[N];
int n,m,head[N],tot,dis[N];
inline void Add(int u,int v,int d)
{
    e[++tot].nxt=head[u];head[u]=tot;e[tot].d=d;e[tot].to=v;
}
inline void Change(int u,int k)
{
    f[u].c=k;
    int l=f[u].son.size();
    for(int i=0;i<l;i++) Change(f[u].son[i],k);
}
inline void Work(int u)
{
    int l=f[u].son.size();
    for(int i=0;i<l;i++)
    {
        f[f[u].son[i]].dep=f[u].dep+1;
        Work(f[u].son[i]);
    }
}
inline void Cut(int u)
{
    int fa=f[u].fa;
    int l=f[fa].son.size();
    for(int i=0;i<l;i++)
    {
        if(u==f[fa].son[i])
        {
            for(int j=i;j<l-1;j++) f[fa].son[j]=f[fa].son[j+1];
            f[fa].son.pop_back();
            break;
        }
    }
    f[u].fa=0; // 自由! !
}
inline void Link(int v,int u)
{
    f[v].dep=f[u].dep+1;
    f[v].fa=u;
    Work(v);
    f[u].son.push_back(v);
}
inline bool Lca(int u,int goal)
{
    if(u==goal) return 1;
    int l=f[u].son.size();
    for(int i=0;i<l;i++)
    {
        if( Lca( f[u].son[i] , goal ) ) return 1;
    }
    return 0;
}
inline bool Spfa()
{
    queue < int > q;q.push(1);
    memset(dis,127/3,sizeof(dis));
    dis[1]=0;f[1].c=f[1].dep=1;
    while(!q.empty())
    {
        int u=q.front();q.pop();
        if(!f[u].c) continue;  //不存在潜力
        for(int j=head[u];j;j=e[j].nxt)
        {
            int v=e[j].to,d=e[j].d;
            if(dis[v]>dis[u]+d)
            {
                if(Lca(v,u)) return 1; // 在新儿子中找到自己就存在负环
                dis[v]=dis[u]+d;
                q.push(v);
                Change(v,0);
                f[v].c=1;
                Cut(v);
                Link(v,u);
            }
        }
    }
    return 0;
}
int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        memset(head,0,sizeof(head));tot=0;
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++)  // 初始化
        {
            f[i].son.clear();
            f[i].fa=f[i].c=f[i].dep=0;
        }
        for(int i=1,u,v,d;i<=m;i++)
        {
            scanf("%d%d%d",&u,&v,&d);
            if(d>=0) Add(u,v,d),Add(v,u,d);
            else Add(u,v,d);
        }
        if(Spfa()) printf("YE5\n");
        else printf("N0\n");
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/jk_chen_acmer/article/details/87710083