• 差分约束


    差分约束,就是用spfa解决一下不等式问题

    但是,这些不等式的不等号必须统一

    当一个不等式形如:sx-sy<=z

    那么我们建一条由y到x权值为z的边

    有一系列不等式组,按此规则建边(如果有>=就取负在做

    然后,得到了一张有向图,对他跑最短路

    跑出来的值,一定满足约束条件

    这就是差分约束的原理


    至于实现,一般按以下规则:

    建立一个类似前缀和数组,得出不等式关系

    然后,按题目要求化为同一类不等式,跑最路

    最小值  -->      >=     --> 最长路

    最大值  -->      <=     --> 最短路


    以P1250种树来分析

    题目:https://www.luogu.com.cn/problem/P1250

    我们设si为到第i位共有多少树

    得s[e]-s[b-1]>=t

    0<=s[i]-s[i-1]<=1

    变成>=的不等式:

    s[e]-s[b-1]>=t

    s[i]-s[i-1]>=0

    s[i-1]-s[i]>=-1

    建边,跑最长路

    #include <bits/stdc++.h>
    using namespace std;
    const int N = 3e4+12;
    int n,m,s[N];
    struct edge
    {
        int next,to,w;
    }p[4*N];
    int head[N],num;
    void ad(int x,int y,int z)
    {
        p[++num]=edge{head[x],y,z};
        head[x]=num;
    }
    int dis[N];
    bool vis[N];
    void spfa()
    {
        queue<int> q;
        memset(dis,143,sizeof(dis));
        q.push(0);
        dis[0]=0;
        while(!q.empty())
        {
            int u=q.front();q.pop();vis[u]=0;
            for(int i=head[u];i;i=p[i].next)
            {
                int v=p[i].to;
                if(dis[v]<dis[u]+p[i].w)
                {
                    dis[v]=dis[u]+p[i].w;
                    if(!vis[v]) vis[v]=1,q.push(v);
                }
            //    printf("%d %d %d
    ",u,v,dis[v]);
            }
        }
    }
    int main()
    {
        cin>>n>>m;
        for(int i=1;i<=m;i++)
        {
            int x,y,z;
            scanf("%d%d%d",&x,&y,&z);
            x--;
            ad(x,y,z);
        //    ad(y,x,y-x);
        }
        for(int i=1;i<=n;i++) ad(i,i-1,-1),ad(i-1,i,0);
        spfa();
        printf("%d
    ",max(dis[n],0));
        return 0;
    }

    再看一道题:

    lgP1993: https://www.luogu.com.cn/problem/P1993

    观察这道题,发现他要维护一堆约束条件:

    a-b>=c

    b-a<=c

    a=b

    有那么多不等式,而且基本上都是在维护这些不等式,我们想到了差分约束

    等等!!!你可能会问:不还有一个等式吗?

    所以我们要把等式转成不等式:

    a=b  --->   a>=b&&b<=a

    这样我们就可以差分约束了

    然后我们就发现:图是不连通的

    所以我们对所有连通块进行搜索

    只要有一个连通块有负环就不行

    #include <bits/stdc++.h>
    using namespace std;
    const int N = 1e4+4;
    int n,m;
    struct edge
    {
        int next,to,w;
    }p[4*N];
    int head[N],num;
    void ad(int x,int y,int z)
    {
        p[++num]=edge{head[x],y,z};
        head[x]=num;
    }
    int dis[N],cnt[N];
    bool vis[N],vs[N];
    bool spfa(int x)
    {
        queue<int> q;
        q.push(x);
        dis[x]=0;
        vs[x]=1;
        while(!q.empty())
        {
            int u=q.front();q.pop();vis[u]=0;
            if(cnt[u]>=n) return 0;
            for(int i=head[u];i;i=p[i].next)
            {
                int v=p[i].to;
                vs[v]=1;
                if(dis[v]>dis[u]+p[i].w)
                {
                    dis[v]=dis[u]+p[i].w;
                    if(!vis[v])
                    {
                        cnt[v]++;
                        if(cnt[v]>=n) return 0;
                        vis[v]=1;
                        q.push(v);
                    }
                }
            }
        }
        return 1;
    }
    bool check()
    {
        memset(dis,127,sizeof(dis));
        memset(vis,0,sizeof(vis));
        memset(cnt,0,sizeof(cnt));
        for(int i=1;i<=n;i++)
            if(!vs[i]) if(!spfa(i)) return 0;
        return 1;
    }
    int main()
    {
        cin>>n>>m;
        for(int i=1;i<=m;i++)
        {
            int opt,a,b,c;
            scanf("%d%d%d",&opt,&a,&b);
            if(opt==1)
            {
                scanf("%d",&c);
                ad(a,b,-c);
            }
            if(opt==2)
            {
                scanf("%d",&c);
                ad(b,a,c);
            }
            if(opt==3)
            {
                ad(b,a,0);
                ad(a,b,0);
            }
        }
        if(check()) puts("Yes");
        else puts("No");
        return 0;
    }
    /*
    钦定一种不等式,在每个连通块中差分约束
    
    a=b   --->    a>=b&a<=b
    钦定了<=跑最短路,看有没有负环
    优化:无用memset,连通块染色,在一个连通块内判断,用连通块的大小来判负环 
    */

    但是一交:70TLE

    说明我们还是too young

    要进一步优化:

    我们发现在一个联通块内有负环,只要入队次数大于连通块大小就可以了

    所以我们可以O(n)预处理出所有连通块的大小

    dfs处理连通块,bfs-spfa,最坏2s,卡过

    #include <bits/stdc++.h>
    using namespace std;
    const int N = 1e4+4;
    int n,m;
    struct edge
    {
        int next,to,w;
    }p[4*N];
    int head[N],num;
    void ad(int x,int y,int z)
    {
        p[++num]=edge{head[x],y,z};
        head[x]=num;
    }
    int dis[N],cnt[N],col[N],cl,sz[N],g[N];
    bool vis[N],vs[N];
    bool spfa(int x)
    {
        queue<int> q;
        q.push(g[x]);
        dis[g[x]]=0;
        while(!q.empty())
        {
            int u=q.front();q.pop();vis[u]=0;
            if(cnt[u]>sz[col[u]]) return 0;
            for(int i=head[u];i;i=p[i].next)
            {
                int v=p[i].to;
                if(dis[v]>dis[u]+p[i].w)
                {
                    dis[v]=dis[u]+p[i].w;
                    if(!vis[v])
                    {
                        cnt[v]++;
                        if(cnt[v]>sz[col[u]]) return 0;
                        vis[v]=1;
                        q.push(v);
                    }
                }
            }
        }
        return 1;
    }
    bool check()
    {
        memset(dis,127,sizeof(dis));
        memset(vis,0,sizeof(vis));
        memset(cnt,0,sizeof(cnt));
        for(int i=1;i<=cl;i++)
            if(!spfa(i)) return 0;
        return 1;
    }
    void dfs(int u)
    {
        col[u]=cl;
        sz[cl]++;
        for(int i=head[u];i;i=p[i].next)
        {
            int v=p[i].to;
            if(!col[v]) dfs(v);
        }
    }
    int main()
    {
        cin>>n>>m;
        for(int i=1;i<=m;i++)
        {
            int opt,a,b,c;
            scanf("%d%d%d",&opt,&a,&b);
            if(opt==1)
            {
                scanf("%d",&c);
                ad(a,b,-c);
            }
            if(opt==2)
            {
                scanf("%d",&c);
                ad(b,a,c);
            }
            if(opt==3)
            {
                ad(b,a,0);
                ad(a,b,0);
            }
        }
        for(int i=1;i<=n;i++)
            if(!col[i])
            {
                cl++;
                g[cl]=i;
                dfs(i);
            }
        if(check()) puts("Yes");
        else puts("No");
        return 0;
    }
    /*
    钦定一种不等式,在每个连通块中差分约束
    
    a=b   --->    a>=b&a<=b
    钦定了<=跑最短路,看有没有负环
    优化:无用memset,连通块染色,在一个连通块内判断,用连通块的大小来判负环 
    */
  • 相关阅读:
    CF G. Running Competition (NTT, 思维)
    ABC 177 F
    牛客练习赛68 D.牛牛的粉丝 (期望DP,矩阵快速幂)
    CF E
    HDU 6761 Minimum Index (字符串--Lyndon分解)
    D. GameGame (思维、博弈)
    P2533 最小圆覆盖
    P4049 [JSOI2007]合金
    P2510 [HAOI2008]下落的圆盘
    P3205 [HNOI2010]合唱队
  • 原文地址:https://www.cnblogs.com/shenbear/p/12220641.html
Copyright © 2020-2023  润新知