差分约束系统

今天初次学习差分约束系统,很神奇的东西

定义:

如果一个系统由n个变量和m个约束条件组成,其中每个约束条件形如xj-xi<=bk(i,j∈[1,n],k∈[1,m]),则称其为差分约束系统(system of difference constraints)。亦即,差分约束系统是求解关于一组变量的特殊不等式组的方法。
求解差分约束系统,可以转化成图论的单源最短路径(或最长路径)问题。
 
其实在一些问题中需求最小值或最大值,同时需要满足一系列的不等式关系,这样就可以用到差分约束系统了,对于求最小值的问题,可以将问题转化为最长路问题;求最大值可以转化为最短路问题。唯一需要注意的就是要找全题中的不等关系,建立正确的边的关系,通常情况下一遍spfa就可以漂亮的解决较为困难的题目。对于大数据也可以很快的出解。
几个小练习:
 
CODEVS1653 种树2
题目描述 Description

一条街的一边有几座房子。因为环保原因居民想要在路边种些树。路边的地区被分割成块,并被编号为1…n。每个块的大小为一个单位尺寸并最多可种一裸树。每个居民想在门前种些树并指定了三个号码b,e,t。这三个数表示该居民想在b和e之间最少种t棵树。当然,b≤e,居民必须保证在指定地区不能种多于地区被分割成块数的树,即要求T≤ e-b+1。允许居民想种树的各自区域可以交叉。出于资金短缺的原因,环保部门请你求出能够满足所有居民的要求,需要种树的最少数量。

思路:可以将这个题目借助前缀和数组转化到一系列的不等式关系。若用f[i]表示到i所用的树木数,f[e]-f[b]>=t(建一条从b到e的长度为t的边权),在题目中还有一个隐含条件,就是0<=f[i]-f[i-1]<=1,这个隐含关系中可以将f[i]与f[i-1]与0、1建立关系,又多了很多条边。对于<=的情况可以同时乘-1,就转变成了>=,进而用最长路求最小值。

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int next[1000000]={0},point[30001]={0},en[1000000]={0},va[1000000]={0},dis[30001]={0},que[1000001]={0};
bool visit[30001]={false};
int main()
{
    int i,j,n,h,b,e,t,tot=0,head,tail,y,x;
    scanf("%d%d",&n,&h);
    for (i=1;i<=h;++i)
    {
        scanf("%d%d%d",&b,&e,&t);
        --b;
        ++tot;
        next[tot]=point[b];
        point[b]=tot;
        en[tot]=e;
        va[tot]=t;
    }
    for (i=1;i<=n;++i)
    {
        ++tot;
        next[tot]=point[i-1];
        point[i-1]=tot;
        en[tot]=i;
        va[tot]=0;
        ++tot;
        next[tot]=point[i];
        point[i]=tot;
        en[tot]=i-1;
        va[tot]=-1;
    }
    memset(dis,128,sizeof(dis));
    dis[0]=0;head=0;tail=1;
    visit[0]=true;que[1]=0;
    do{
        head=head%1000000+1;
        x=que[head];
        visit[x]=false;
        y=point[x];
        while (y!=0)
        {
            if (dis[x]+va[y]>dis[en[y]])
            {
                dis[en[y]]=dis[x]+va[y];
                if (!visit[en[y]])
                {
                    visit[en[y]]=true;
                    tail=tail%1000000+1;
                    que[tail]=en[y];
                }
            }
            y=next[y];
        }
    }while(head!=tail);
    printf("%d
",dis[n]);
}
View Code

CODEVS1768种树3

题目描述 Description

为了绿化乡村,H村积极响应号召,开始种树了。

H村里有n幢房屋,这些屋子的排列顺序很有特点,在一条直线上。于是方便起见,我们给它们标上1~n。树就种在房子前面的空地上。

同时,村民们向村长提出了m个意见,每个意见都是按如下格式:希望第li个房子到第ri个房子的房前至少有ci棵树。

因为每个房屋前的空地面积有限,所以每个房屋前最多只能种ki棵树

村长希望在满足村民全部要求的同时,种最少的树以节约资金。请你帮助村长。

思路:和上一题的大致思路一样,不过有一点不同,0<=f[i]-f[i-1]<=k[i],其他都相同。

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int next[5000000]={0},point[500001]={0},en[5000000]={0},va[5000000]={0},k[500001]={0},que[10000001]={0};
long long dis[500001]={0};
bool visit[500001]={false};
int main()
{
    int i,j,n,m,lp,rp,cp,head,tail,tot=0,x,y;
    scanf("%d%d",&n,&m);
    for (i=1;i<=n;++i)
      scanf("%d",&k[i]);
    for (i=1;i<=m;++i)
    {
        scanf("%d%d%d",&lp,&rp,&cp);
        --lp;
        ++tot;
        next[tot]=point[lp];
        point[lp]=tot;
        en[tot]=rp;
        va[tot]=cp;
    }
    for (i=1;i<=n;++i)
    {
        ++tot;
        next[tot]=point[i-1];
        point[i-1]=tot;
        en[tot]=i;
        va[tot]=0;
        ++tot;
        next[tot]=point[i];
        point[i]=tot;
        en[tot]=i-1;
        va[tot]=-k[i];
    }
    head=0;tail=1;que[1]=0;
    memset(dis,128,sizeof(dis));
    dis[0]=0;visit[0]=true;
    do{
        head=head%10000000+1;
        x=que[head];
        visit[x]=false;
        y=point[x];
        while(y!=0)
        {
            if (dis[x]+va[y]>dis[en[y]])
            {
                dis[en[y]]=dis[x]+va[y];
                if (!visit[en[y]])
                {
                    visit[en[y]]=true;
                    tail=tail%10000000+1;
                    que[tail]=en[y];
                }
            }
            y=next[y];
        }
    }while(head!=tail);
    printf("%lld
",dis[n]);
}
View Code

 poj3159Candies

题目大意:已知a、b两名小朋友间的差值关系(b-a<=c),求n-1的最大值。

思路:又变成了赤裸裸的差分约束系统,a到b的边权为c,搜最短路就可以了。但是这个题的数据卡spfa+queue,从网上学习到spfa可以用栈实现,于是就ac了。(太长知识了,从不知道stack可以实现spfa。。。)

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int next[3000000]={0},point[300001]={0},en[3000000]={0},va[3000000]={0},que[1000001]={0};
long long dis[300001]={0};
bool visit[300001]={false};
int main()
{
    int i,j,n,m,tot=0,a,b,c,top=0,x,y;
    scanf("%d%d",&n,&m);
    for (i=1;i<=m;++i)
    {
        scanf("%d%d%d",&a,&b,&c);
        ++tot;
        next[tot]=point[a];
        point[a]=tot;
        en[tot]=b;
        va[tot]=c;
    }
    top=1;que[1]=1;visit[1]=true;
    memset(dis,127,sizeof(dis));dis[1]=0;
    while(top>0)
    {
        x=que[top];
        --top;
        visit[x]=false;
        y=point[x];
        while(y!=0)
        {
            if (dis[en[y]]>dis[x]+va[y])
            {
                dis[en[y]]=dis[x]+va[y];
                if (!visit[en[y]])
                {
                    visit[en[y]]=true;
                    ++top;
                    que[top]=en[y];
                }
            }
            y=next[y];
        }
    }
    printf("%lld
",dis[n]);
}
View Code

poj1716&&1201 Intervals

以1201为题目要求,进行分析。

题目大意:给定n个闭区间,从每个区间中选择整数,满足给定的要求(不同区间选择个数不同),使得所取个数最小。

思路:沿用codevs上种树的思路,sum[i]数组表示到i位时取得整数的个数,对于一个区间[a,b],sum[b]-sum[a-1]>=c,同时,题目中有隐含条件,0<=sum[i]-sum[i-1]<=1(1<=i<=maxn),因为a可能为0,所以可以将整个区间右移一个单位。从0到maxn的取值范围,一遍最长路解决。

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int next[100000]={0},point[10010]={0},en[100000]={0},va[100000]={0},dis[10010]={0},que[1000010]={0};
bool visit[10010]={false};
int main()
{
    int x,y,n,i,j,a,b,head,tail,maxn=0,tot=0;
    scanf("%d",&n);
    for (i=1;i<=n;++i)
    {
        scanf("%d%d",&a,&b);
        ++b;
        if (b>maxn) maxn=b;
        ++tot;
        next[tot]=point[a];
        point[a]=tot;
        en[tot]=b;
        va[tot]=2;
    }
    for (i=1;i<=maxn;++i)
    {
        ++tot;next[tot]=point[i-1];point[i-1]=tot;en[tot]=i;va[tot]=0;
        ++tot;next[tot]=point[i];point[i]=tot;en[tot]=i-1;va[tot]=-1;
    }
    head=0;tail=1;que[1]=0;visit[0]=true;
    memset(dis,128,sizeof(dis));dis[0]=0;
    do{
        head=head%1000000+1;
        x=que[head];
        visit[x]=false;
        y=point[x];
        while(y!=0)
        {
            if (dis[x]+va[y]>dis[en[y]])
            {
                dis[en[y]]=dis[x]+va[y];
                if (!visit[en[y]])
                {
                    tail=tail%1000000+1;
                    que[tail]=en[y];
                    visit[en[y]]=true;
                }
            }
            y=next[y];
        }
    }while(head!=tail);
    printf("%d
",dis[maxn]);
}
Integer Intervals
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int next[1000000]={0},point[50010]={0},en[1000000]={0},va[1000000]={0},dis[50010]={0},que[1000010]={0};
bool visit[50010]={false};
int main()
{
    int x,y,n,i,j,a,b,c,head,tail,maxn=0,tot=0;
    scanf("%d",&n);
    for (i=1;i<=n;++i)
    {
        scanf("%d%d%d",&a,&b,&c);
        ++b;
        if (b>maxn) maxn=b;
        ++tot;
        next[tot]=point[a];
        point[a]=tot;
        en[tot]=b;
        va[tot]=c;
    }
    for (i=1;i<=maxn;++i)
    {
        ++tot;next[tot]=point[i-1];point[i-1]=tot;en[tot]=i;va[tot]=0;
        ++tot;next[tot]=point[i];point[i]=tot;en[tot]=i-1;va[tot]=-1;
    }
    head=0;tail=1;que[1]=0;visit[0]=true;
    memset(dis,128,sizeof(dis));dis[0]=0;
    do{
        head=head%1000000+1;
        x=que[head];
        visit[x]=false;
        y=point[x];
        while(y!=0)
        {
            if (dis[x]+va[y]>dis[en[y]])
            {
                dis[en[y]]=dis[x]+va[y];
                if (!visit[en[y]])
                {
                    tail=tail%1000000+1;
                    que[tail]=en[y];
                    visit[en[y]]=true;
                }
            }
            y=next[y];
        }
    }while(head!=tail);
    printf("%d
",dis[maxn]);
}
Intervals

poj3169Layout

题目大意:一群牛要排队,给出两头牛之间距离的最大值和最小值,求所能排的最长距离。

思路:这个题目比较简单,但是有一点变化,就是可能会出现答案无限大的情况。其实这些条件比较好判断。如果出现负环就是无解,输出-1;如果最终结果是无限大就是输出-2;其余的直接输出就可以了。对于简单的差分约束系统的练习的变式,也要发现本质,从容应对。

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int next[30000]={0},point[5000]={0},que[1000001]={0},en[30000]={0},va[30000]={0},use[5000]={0},dis[5000]={0};
bool visit[5000]={false};
int main()
{
    int x,y,n,ml,md,i,j,tot=0,head,tail,a,b,d,maxn;
    bool ff=false;
    maxn=2100000000;
    scanf("%d%d%d",&n,&ml,&md);
    for (i=1;i<=ml;++i)
    {
        scanf("%d%d%d",&a,&b,&d);
        ++tot;next[tot]=point[a];
        point[a]=tot;en[tot]=b;va[tot]=d;
    }
    for (i=1;i<=md;++i)
    {
        scanf("%d%d%d",&a,&b,&d);
        ++tot;next[tot]=point[b];
        point[b]=tot;en[tot]=a;va[tot]=-d;
    }
    for (i=2;i<=n;++i)
    {
        ++tot;next[tot]=point[i];
        point[i]=tot;en[tot]=i-1;va[tot]=0;
    }
    head=0;tail=1;que[1]=1;visit[1]=true;use[1]=1;
    memset(dis,127,sizeof(dis));dis[1]=0;
    do{
        head=head%1000000+1;
        x=que[head];
        y=point[x];
        visit[x]=false;
        while(y!=0)
        {
            if (dis[x]+va[y]<=dis[en[y]])
            {
                dis[en[y]]=dis[x]+va[y];
                if (!visit[en[y]])
                {
                    tail=tail%1000000+1;
                    que[tail]=en[y];
                    ++use[en[y]];
                    if (use[en[y]]>n)
                    {
                        ff=true;
                        break;
                    }
                    visit[en[y]]=true;
                }
            }
            y=next[y];
        }
        if (ff) break;
    }while(head!=tail);
    if (ff) printf("-1
");
    else
    {
        if (dis[n]>maxn) printf("-2
");
        else printf("%d
",dis[n]);
    }
}
View Code

poj1275Cashier Employment

题目大意:给出各个小时内所需人手的个数,给出n个应聘者和他们开始工作的时间,求最少需要的总人数。

思路:r[i]表示个小时需要的人、st[i]表示从i时刻开始工作的人数、sum[i]表示到i为止应聘的总人数。

        很容易就能写出三个不等式:1)0<=sum[i]-sum[i-1]<=st[i]

                                            2)sum[i]-sum[i-8]>=r[i](9<=i<=24)

                                            3)sum[i]+sum[24]-sum[i+16]>=r[i](1<=i<=8)   ==>变形后sum[i]-sum[i+16]>=r[i]-ans(因为sum[24]与i无                                                 关,所以可以用ans代替、移项、穷举,进行求解)

        下面问题来了。这样做,就是wa,那么我们拉了些什么呢?sum[24]-sum[0]>=ans,最后判断sum[24]是否等于ans就可以了,若等于,就直接输出,否则         继续做。

        一开始清了一个数组,tle。。。tle。。。了好久,终于ac了!!!

其实这个题目是比较复杂的差分约束,如果能想出前三个不等式,也很难全做对,所以要挖掘隐含条件,多练习

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int next[1000]={0},point[25]={0},en[1000]={0},va[1000]={0},st[25]={0},dis[25]={0},que[1000001]={0},
    use[25]={0},r[25]={0};
bool visit[25]={0};
int main()
{
    int n,i,j,x,y,head,tail,tot=0,ci;
    bool ff=false,f=false;
    scanf("%d",&ci);
    while(ci>0)
    {
    --ci;
    memset(r,0,sizeof(r));
    memset(st,0,sizeof(st));
    ff=false;
    for (i=1;i<=24;++i)
        scanf("%d",&r[i]);
    scanf("%d",&n);
    for (i=1;i<=n;++i)
    {
      scanf("%d",&x);
      ++x;
      ++st[x];
    }
    for (i=0;i<=n;++i)
    {
        memset(next,0,sizeof(next));
        memset(point,0,sizeof(point));
        memset(dis,128,sizeof(dis));
        memset(visit,false,sizeof(visit));
        memset(use,0,sizeof(use));
        tot=0;
        for (j=1;j<=24;++j)
        {
            ++tot;next[tot]=point[j-1];point[j-1]=tot;en[tot]=j;va[tot]=0;
            ++tot;next[tot]=point[j];point[j]=tot;en[tot]=j-1;va[tot]=-st[j];
            if (j>8)
            {
                ++tot;next[tot]=point[j-8];point[j-8]=tot;en[tot]=j;va[tot]=r[j];
            }
            else
            {
                ++tot;next[tot]=point[j+16];point[j+16]=tot;en[tot]=j;va[tot]=r[j]-i;
            }
        }
        ++tot;next[tot]=point[0];point[0]=tot;en[tot]=24;va[tot]=i;
        head=0;tail=1;que[1]=0;visit[0]=true;dis[0]=0;++use[0];f=false;
        do{
            head=head%1000000+1;
            x=que[head];
            visit[x]=false;
            y=point[x];
            while(y!=0)
            {
                if (dis[x]+va[y]>dis[en[y]])
                {
                    dis[en[y]]=dis[x]+va[y];
                    if (!visit[en[y]])
                    {
                        visit[en[y]]=true;
                        ++use[en[y]];
                        if (use[en[y]]>24)
                        {
                            f=true;
                            break;
                        }
                        tail=tail%1000000+1;
                        que[tail]=en[y];
                    }
                }
                y=next[y];
            }
            if (f) break;
        }while(head!=tail);
        if (!f)
          if (dis[24]==i)
          {
            ff=true;
            break;
          }
    }
    if (ff) printf("%d
",i);
    else printf("No Solution
");
    }
}
View Code

poj2983Is the Information Reliable?

题目大意:给出一系列的不等式关系,判断是否可行。

思路:裸裸的差分约束,但是因为不能保证这是一个连通图,所以要做连通块的个数边spfa(一开始做了n遍,结果超时了),若有负环就输出无解。

         这个题有一点不同,就是可能两个城堡之间有确切的距离,这个其实很简单,只要将c<=sum[b]-sum[a]<=c,加入这两条边就可以了。

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int next[200001]={0},point[1001]={0},en[200001]={0},va[200001]={0},que[1000001]={0},use[1001]={0},dis[1000]={0};
bool visit[1001]={false},vv[1001]={false};
int main()
{
    int n,m,i,j,a,b,c,tot=0,x,y,head,tail;
    bool ff=false,f=false;
    char ch;
    while(scanf("%d%d",&n,&m)==2)
    {
      memset(next,0,sizeof(next));
      memset(point,0,sizeof(point));
      memset(vv,false,sizeof(vv));
      tot=0;f=false;
      for (i=1;i<=m;++i)
      {
        scanf("%*c%c%d%d",&ch,&a,&b);
        if (ch=='P')
        {
            scanf("%d",&c);
            ++tot;next[tot]=point[a];point[a]=tot;en[tot]=b;va[tot]=c;
            ++tot;next[tot]=point[b];point[b]=tot;en[tot]=a;va[tot]=-c;
        }
        else
        {
            ++tot;next[tot]=point[b];point[b]=tot;en[tot]=a;va[tot]=-1;
        }
      }
      for (i=1;i<=n;++i)
      if (!vv[i])
      {
        memset(visit,false,sizeof(visit));
        memset(use,0,sizeof(use));
        memset(dis,127,sizeof(dis));
        ff=false;
        head=0;tail=1;que[1]=i;dis[i]=0;visit[i]=true;++use[i];vv[i]=true;
        do{
            head=head%1000000+1;
            x=que[head];
            y=point[x];
            visit[x]=false;
            while(y!=0)
            {
                if (dis[x]+va[y]<dis[en[y]])
                {
                    dis[en[y]]=va[y]+dis[x];
                    if (!visit[en[y]])
                    {
                        tail=tail%1000000+1;
                        que[tail]=en[y];
                        vv[en[y]]=true;
                        visit[en[y]]=true;
                        ++use[en[y]];
                        if (use[en[y]]>n)
                        {
                            ff=true;
                            break;
                        }
                    }
                }
                y=next[y];
            }
            if (ff) break;
        }while(head!=tail);
        if (ff) 
        {
            f=true;break;
        }
      }
      if (f) printf("Unreliable
");
      else printf("Reliable
");
    }
}
View Code

cogs286 【noi1999】01串

思路:题目要求出一个满足所有条件的01串,这有转化成了差分约束系统(越来越神奇了)。sum[i]表示到i位置的0的个数(记住是0的个数,一开始输出反了,结果只得了20分。。。)

        我们得出了三个不等式:1)a0<=sum[i]-sum[i-l0]<=b0;

                                       2)a1<=sum1[i]-sum1[i-l1]<=b1(可以转化为sum与a1、b1、l1之间的关系==>l1-b1<=sum[i]-sum[i-l1]<=l1-a1)

                                       3)0<=sum[i]-sum[i-1]<=1(隐含条件)

        spfa一遍最短路,如果有负环就输出-1,否则就根据sum[i]与sum[i-1]之间的关系输出0||1。

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int next[10000]={0},point[1001]={0},en[10000]={0},va[10000]={0},dis[1001]={0},use[1001]={0},que[1000001]={0};
bool visit[1001]={false};
int main()
{
    freopen("sequence.in","r",stdin);
    freopen("sequence.out","w",stdout);
    
    int n,i,j,tot=0,a0,b0,l0,a1,b1,l1,head,tail,x,y;
    bool ff=false;
    scanf("%d%d%d%d%d%d%d",&n,&a0,&b0,&l0,&a1,&b1,&l1);
    for (i=l0;i<=n;++i)
    {
        ++tot;next[tot]=point[i-l0];point[i-l0]=tot;en[tot]=i;va[tot]=b0;
        ++tot;next[tot]=point[i];point[i]=tot;en[tot]=i-l0;va[tot]=-a0;
    }
    for (i=l1;i<=n;++i)
    {
        ++tot;next[tot]=point[i-l1];point[i-l1]=tot;en[tot]=i;va[tot]=l1-a1;
        ++tot;next[tot]=point[i];point[i]=tot;en[tot]=i-l1;va[tot]=b1-l1;
    }
    for (i=1;i<=n;++i)
    {
        ++tot;next[tot]=point[i-1];point[i-1]=tot;en[tot]=i;va[tot]=1;
        ++tot;next[tot]=point[i];point[i]=tot;en[tot]=i-1;va[tot]=0;
    }
    memset(dis,127,sizeof(dis));dis[0]=0;visit[0]=true;
    head=0;tail=1;que[1]=0;++use[0];
    do{
        head=head%1000000+1;
        x=que[head];
        y=point[x];
        visit[x]=false;
        while(y!=0)
        {
            if (dis[x]+va[y]<dis[en[y]])
            {
                dis[en[y]]=dis[x]+va[y];
                if (!visit[en[y]])
                {
                    visit[en[y]]=true;
                    ++use[en[y]];
                    if (use[en[y]]>n)
                    {
                        ff=true;
                        break;
                    }
                    tail=tail%1000000+1;
                    que[tail]=en[y];
                }
            }
            y=next[y];
        }
        if (ff) break;
    }while(head!=tail);
    if (ff) printf("-1
");
    else
    {
        for (i=1;i<=n;++i)
        {
            if (dis[i]-dis[i-1]==1) printf("0");
            else printf("1");
        }
        printf("
");
    }
    
    fclose(stdin);
    fclose(stdout);
}
View Code

cogs【ceoi】数列问题

思路:题目要求每连续p个数相加>0,每连续q个数相加<0,输出一个满足的序列,或者无解。

        和上一题想似,不过转化成了sum[i]表示1到i的和,从而写出不等式:

        1)sum[i]-sum[i-p]>=1;

        2)sum[i]-sum[i-q]<=-1;

        连边后,本想直接spfa,后来发现一个很可怕的问题,就是有的点入度为0,若从0开始,则那些入度为0的点的dis值为正无穷,无法求解。所以这里用到了拓扑排序的思想,找到入度为0的点进行spfa,这样能保证每次的搜索都不会有交集。但是又有一个问题(第一次教上之后wa了一个点的原因),所有点都入度>0,这时,我们从任意一个点开始spfa就可以了。

(不要以为有了思路就可以ac了,要注意细节啊!!!)

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int next[1000]={0},point[101]={0},en[1000]={0},va[1000]={0},que[1000001]={0},use[101]={0},ru[101]={0},dis[101]={0};
bool visit[101]={false},vv[101]={false};
int main()
{
    freopen("topoa.in","r",stdin);
    freopen("topoa.out","w",stdout);
    
    int n,p,q,i,j,x,y,head,tail,tot=0;
    bool ff=false,f=false;
    scanf("%d%d%d",&n,&p,&q);
    for (i=p;i<=n;++i)
    {
        ++tot;next[tot]=point[i];point[i]=tot;en[tot]=i-p;va[tot]=-1;
        ++ru[i-p];
    }
    for (i=q;i<=n;++i)
    {
        ++tot;next[tot]=point[i-q];point[i-q]=tot;en[tot]=i;va[tot]=-1;
        ++ru[i];
    }
    memset(dis,127,sizeof(dis));
    for (i=0;i<=n;++i)
    {
        if (ru[i]==0)
        {
            f=true;
            memset(visit,false,sizeof(visit));
            visit[i]=true;dis[i]=0;++use[i];que[1]=i;head=0;tail=1;
            do{
                head=head%1000000+1;
                x=que[head];
                visit[x]=false;
                y=point[x];
                while(y!=0)
                {
                    if (dis[x]+va[y]<dis[en[y]])
                    {
                        dis[en[y]]=dis[x]+va[y];
                        if (!visit[en[y]])
                        {
                            tail=tail%1000000+1;
                            que[tail]=en[y];
                            visit[en[y]]=true;
                            ++use[en[y]];
                            if (use[en[y]]>n)
                            {
                                ff=true;
                                break;
                            }
                        }
                    }
                    y=next[y];
                }
                if (ff) break;
            }while(head!=tail);
        }
        if (ff) break;
        if (!f&&i==n)
        {
            ru[n]=0;--i;
        }
    }
    if (ff) printf("no
");
    else
    {
        for (i=1;i<=n;++i)
          printf("%d ",dis[i]-dis[i-1]);
        printf("
");
    }
    
    fclose(stdin);
    fclose(stdout);
}
View Code

cogs1787月考统计

题目大意:给定多组学生之间成绩关系,判断每名学生和最低成绩的差值的最小值。

思路:可以写出比较简单的不等式:sum[i]-sum[j]<=c。但这里可能图不连通,或者不知道从那个点开始spfa,于是,我们又有一个小技巧:虚设一个点0,将

0与所有点相连,使边权为0,这是0就相当于最底分数,其他的点到0的距离dis[i]即为答案。

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int next[20000]={0},point[1001]={0},en[20000]={0},va[20000]={0},que[1000001]={0},use[1001]={0},dis[1001]={0};
bool visit[1001]={false},vv[1001]={false};
int main()
{
    freopen("ExamStat.in","r",stdin);
    freopen("ExamStat.out","w",stdout);
    
    int n,m,i,j,x,y,c,head,tail,tot=0;
    bool ff=false;
    scanf("%d%d",&n,&m);
    for (i=1;i<=m;++i)
    {
        scanf("%d%d%d",&x,&y,&c);
        vv[x]=vv[y]=true;
        ++tot;next[tot]=point[x];point[x]=tot;en[tot]=y;va[tot]=-c;
    }
    for (i=1;i<=n;++i)
    {
        ++tot;next[tot]=point[0];point[0]=tot;en[tot]=i;va[tot]=0;
    }
    head=0;tail=1;que[1]=0;
    memset(dis,128,sizeof(dis));
    dis[0]=0;visit[0]=true;++use[0];
    do{
        head=head%1000000+1;
        x=que[head];
        y=point[x];
        visit[x]=false;
        while(y!=0)
        {
            if (dis[x]+va[y]>dis[en[y]])
            {
                dis[en[y]]=dis[x]+va[y];
                if (!visit[en[y]])
                {
                    visit[en[y]]=true;
                    tail=tail%1000000+1;
                    que[tail]=en[y];
                    ++use[en[y]];
                    if (use[en[y]]>n)
                    {
                        ff=true;
                        break;
                    }
                }
            }
            y=next[y];
        }
        if (ff) break;
    }while(head!=tail);
    if (ff) printf("SOMEONE LAY!
");
    else
    {
        for (i=1;i<=n;++i)
        {
          if (vv[i])
            printf("%d ",dis[i]);
          else printf("-1 ");
        }
        printf("
");
    }
    
    fclose(stdin);
    fclose(stdout);
}
View Code

当然也可以用之前的思路,做多遍spfa。不过,有一点需要注意,在每一遍spfa之前的判断中,如果这个点已经访问过但是dis值<0(且不为无穷小)仍可将其作为spfa的起点加入队列,这样就可以求出最终答案了。这样的时间复杂度会比之前的稍大,慢了0.2s左右。(dis数组只需要一次清空就可以了,后面的都会自动更新的。)

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int next[20000]={0},point[1001]={0},en[20000]={0},va[20000]={0},que[1000001]={0},use[1001]={0},dis[1001]={0};
bool visit[1001]={false},vv[1001]={false},viv[1001]={false};
int main()
{
    freopen("ExamStat.in","r",stdin);
    freopen("ExamStat.out","w",stdout);
    
    int n,m,i,j,x,y,c,head,tail,tot=0;
    bool ff=false,f=false;
    scanf("%d%d",&n,&m);
    for (i=1;i<=m;++i)
    {
        scanf("%d%d%d",&x,&y,&c);
        vv[x]=vv[y]=true;
        ++tot;next[tot]=point[x];point[x]=tot;en[tot]=y;va[tot]=-c;
    }
    memset(dis,128,sizeof(dis));
    for (i=1;i<=n;++i)
    if (!viv[i]||(dis[i]<0&&dis[i]>-2100000000))
    {
    viv[i]=true;memset(visit,false,sizeof(visit));
    memset(use,0,sizeof(use));
    head=0;tail=1;que[1]=i;
    dis[i]=0;visit[i]=true;++use[i];
    do{
        head=head%1000000+1;
        x=que[head];
        y=point[x];
        visit[x]=false;
        while(y!=0)
        {
            if (dis[x]+va[y]>dis[en[y]])
            {
                dis[en[y]]=dis[x]+va[y];
                if (!visit[en[y]])
                {
                    visit[en[y]]=true;
                    viv[en[y]]=true;
                    tail=tail%1000000+1;
                    que[tail]=en[y];
                    ++use[en[y]];
                    if (use[en[y]]>n)
                    {
                        ff=true;
                        break;
                    }
                }
            }
            y=next[y];
        }
        if (ff) break;
    }while(head!=tail);
    if (ff) 
    {
        printf("SOMEONE LAY!
");
        f=true;break;
    }
    }
    if (!f)
    {
        for (i=1;i<=n;++i)
        {
          if (vv[i])
            printf("%d ",dis[i]);
          else printf("-1 ");
        }
        printf("
");
    }
    
    fclose(stdin);
    fclose(stdout);
}
View Code

 (个人见解:对于能虚设结点的题目,一定能够用多遍spfa解决(为防止超时,往往需要判断条件);但对于能用多遍spfa解决的不一定能用虚设结点解决。虚设结点的方法在解决所有元素与这些元素中最小值的差的最值问题中应用较多,且相对快。)

vijos1094

思路:建立一个有向图,给出一条边的起点和终点的大小关系(>,=,<),然后就是差分约束了,可以虚设一个0点,方便求解。

#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
int next[100000]={0},point[10001]={0},en[100000]={0},va[100000]={0},dis[10001]={0},use[10001]={0},
    que[1000001]={0};
bool visit[10001]={false};
int main()
{
    int n,m,a,b,c,tot=0,i,j,ans=0,head,tail,x,y;
    bool ff=false;
    scanf("%d%d",&n,&m);
    for (i=1;i<=m;++i)
    {
        scanf("%d%d%d",&a,&b,&c);
        if (c<0)
        {
            ++tot;next[tot]=point[a];point[a]=tot;en[tot]=b;va[tot]=1;
        }
        if (c==0)
        {
            ++tot;next[tot]=point[a];point[a]=tot;en[tot]=b;va[tot]=0;
            ++tot;next[tot]=point[b];point[b]=tot;en[tot]=a;va[tot]=0;
        }
        if (c>0)
        {
            ++tot;next[tot]=point[b];point[b]=tot;en[tot]=a;va[tot]=1;
        }
    }
    for (i=1;i<=n;++i)
    {
        ++tot;next[tot]=point[0];point[0]=tot;en[tot]=i;va[tot]=0;
    }
    memset(dis,128,sizeof(dis));dis[0]=0;que[1]=0;head=0;tail=1;
    visit[0]=true;++use[0];
    do{
        head=head%1000000+1;
        x=que[head];
        y=point[x];
        visit[x]=false;
        while(y!=0)
        {
            if (dis[x]+va[y]>dis[en[y]])
            {
                dis[en[y]]=dis[x]+va[y];
                if (!visit[en[y]])
                {
                    visit[en[y]]=true;
                    tail=tail%1000000+1;
                    que[tail]=en[y];
                    ++use[en[y]];
                    if (use[en[y]]>n)
                    {
                        ff=true;
                        break;
                    }
                }
            }
            y=next[y];
        }
        if (ff) break;
    }while(head!=tail);
    if (ff) printf("NO
");
    else
    {
        for (i=1;i<=n;++i)
          if (dis[i]>ans)
                ans=dis[i];
        printf("%d
",ans);
    }
}
View Code

bzoj1077 天平

题目大意:给定一些砝码,都是1~3g的,和一些大小关系(有的未知,但一定存在一种方案使得其成立),给定a、b放在左边,选出c、d放在右边,求出左边>右边、左边=右边、左边<右边的方案数(只有可能性为1的方案才算做是合法的)。

思路:差分约束+暴力。根据关系可以建出差分约束的图,然后跑两遍floyed求出两两差值的最小、最大值(这里的大小、方向十分重要,连边的时候只需要考虑+或者-就可以了,为了求出最大最小,会对不等号方向进行改变,+-最后是一样的。要添加源汇点,所有点到它们的距离为0,同时它们之间的距离要根据最大最小值相应的改变,但它们一定是一个1g、一个3g),然后枚举c、d是什么,根据最小最大值判断是否关系唯一,统计答案。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define maxm 55
using namespace std;
char si[maxm][maxm];
int di[maxm][maxm],da[maxm][maxm];
char in(){
    char ch=getchar();
    while(ch!='+'&&ch!='-'&&ch!='?'&&ch!='=') ch=getchar();
    return ch;
}
int main(){
    int n,i,j,k,a,b,c1=0,c2=0,c3=0;
    scanf("%d%d%d",&n,&a,&b);
    for (i=1;i<=n;++i)
      for (j=1;j<=n;++j) si[i][j]=in();
    memset(di,-60,sizeof(di));
    di[0][n+1]=2;di[n+1][0]=-2;
    for (i=1;i<=n;++i){
      di[0][i]=di[i][n+1]=0;
      for (j=1;j<=n;++j){
          if (i==j){di[j][i]=0;continue;}
          if (si[i][j]=='+') di[j][i]=1;
          if (si[i][j]=='=') di[j][i]=0;
      }
    }for (k=0;k<=n+1;++k)
        for (i=0;i<=n+1;++i)
          for (j=0;j<=n+1;++j) di[i][j]=max(di[i][k]+di[k][j],di[i][j]);
    memset(da,60,sizeof(da));
    da[0][n+1]=-2;da[n+1][0]=2;
    for (i=1;i<=n;++i){
      da[0][i]=da[i][n+1]=0;
      for (j=1;j<=n;++j){
          if (i==j){da[j][i]=0;continue;}
          if (si[i][j]=='-') da[j][i]=-1;
          if (si[i][j]=='=') da[j][i]=0;
      }
    }for (k=0;k<=n+1;++k)
        for (i=0;i<=n+1;++i)
          for (j=0;j<=n+1;++j) da[i][j]=min(da[i][k]+da[k][j],da[i][j]);
    for (i=1;i<=n;++i){
        if (i==a||i==b) continue;
        for (j=i+1;j<=n;++j){
            if (j==a||j==b) continue;
            if (di[i][a]>da[b][j]||di[j][a]>da[b][i]) ++c1;
            if (di[a][i]>da[j][b]||di[a][j]>da[i][b]) ++c3;
            if ((di[i][a]==da[i][a]&&di[b][j]==da[b][j]&&di[i][a]==di[b][j])||
                (di[j][a]==da[j][a]&&di[b][i]==da[b][i]&&di[j][a]==di[b][i])) ++c2;
        }
    }printf("%d %d %d
",c1,c2,c3);
}
View Code

bzoj4500 矩阵

题目大意:每次可以给一行或一列+1或-1,给出k个格子的限制=c,问能否操作出来。

思路:相当于xi-yi=c,可以建差分约束,建源点向每个点连边0,有环就是No。

注意:spfa判环会从中间退出,所以应该清visit数组。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 4005
#define len 1000000
using namespace std;
int point[N],next[N],en[N],va[N],tot=0,que[len+1],dis[N],ss,vt[N],n,m;
bool vi[N]={false};
void add(int u,int v,int vv){next[++tot]=point[u];point[u]=tot;en[tot]=v;va[tot]=vv;}
bool spfa(){
    int u,i,v,head=0,tail;
    memset(dis,128,sizeof(dis));
    memset(vt,0,sizeof(vt));
    memset(vi,false,sizeof(vi));
    dis[ss]=0;vi[que[tail=1]=ss]=true;
    while(head!=tail){
        vi[u=que[head=head%len+1]]=false;
        for (i=point[u];i;i=next[i]){
            if (dis[v=en[i]]<dis[u]+va[i]){
                dis[v]=dis[u]+va[i];
                if (!vi[v]){
                    vi[que[tail=tail%len+1]=v]=true;
                    if ((++vt[v])>n+m) return false;
                }
            }
        }
    }return true;}
int main(){
    int i,t,k,x,y,c;
    scanf("%d",&t);ss=0;
    while(t--){
        memset(point,0,sizeof(point));tot=0;
        scanf("%d%d%d",&n,&m,&k);
        for (i=n+m;i;--i) add(ss,i,0);
        while(k--){
            scanf("%d%d%d",&x,&y,&c);
            add(y+n,x,c);add(x,y+n,-c);
        }if (spfa()) printf("Yes
");
        else printf("No
");
    }
}
View Code

在今后的学习中可能还会遇到大于或小于的情况,都可以通过所谓的常数(通常是右侧的部分)+1-1来转化为>=<=。

通常情况下最好用spfa,还要判断负环,有的话就不存在。

对于不等式的选取和转化可能是一个难点,但真正能找出题目的本意,正确的套用差分约束系统,才是重点和难点。多加练习吧!!!

原文地址:https://www.cnblogs.com/Rivendell/p/4098257.html