差分约束

差分约束,就是用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\n",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\n",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,连通块染色,在一个连通块内判断,用连通块的大小来判负环 
*/
posted @ 2020-01-21 09:14  shenbear  阅读(170)  评论(0编辑  收藏  举报