差分约束系统
一.定义
差分约束系统(system of difference constraints),是求解关于一组变数的特殊不等式组之方法。如果一个系统由n个变量和m个约束条件组成,形成m个形如ai-aj≤k的不等式(i,j∈[1,n],k为常数),则称其为差分约束系统(system of difference constraints)。亦即,差分约束系统是求解关于一组变量的特殊不等式组的方法。
二.详解
鉴于我是初学差分约束系统,对于概念其实并不是理解得很透彻, 不过等我理解透了,一定要写一篇解释差分约束系统的随笔
在此奉上几位大佬的博客
作者:英雄哪里出来
http://www.cppblog.com/menjitianya/archive/2015/11/19/212292.html
作者:HARD_UNDERSTAND
https://blog.csdn.net/hjt_fathomless/article/details/52463723
三.一道例题
1.题目(p1227)
描述 Description
给出一有向图,图中每条边都被标上了关系运算符‘<’,‘>’,‘=’。现在要给图中每个顶点标上一个大于等于0,小于等于k的某个整数使所有边上的符号得到满足。若存在这样的k,则求最小的k,若任何k都无法满足则输出NO。
例如下表中最小的k为2。
结点1>结点2
结点2>结点3
结点2>结点4
结点3=结点4
如果存在这样的k,输出最小的k值;否则输出‘NO’。
输入格式 Input Format
共二行,第一行有二个空格隔开的整数n和m。n表示G的结点个数,m表示G的边数,其中1<=n<=1000, 0<=m<=10000。全部结点用1到n标出,图中任何二点之间最多只有一条边,且不存在自环。
第二行共有3m个用空格隔开的整数,第3i-2和第3i-1(1<=i<=m)个数表示第i条边的顶点。第3i个数表示第i条边上的符号,其值用集合{-1,0,1}中的数表示:-1表示‘<’, 0 表示‘=’, 1表示‘>’。
输出格式 Output Format
仅一行,如无解则输出‘NO’;否则输出最小的k的值。
样例输入 Sample Input
4 4
1 2 -1 2 3 0 2 4 -1 3 4 -1
样例输出 Sample Output
2
时间限制 Time Limitation
各个测试点1s
2.一些感受
前些日子,由于找死,非得学最短路的四个算法(虽然现在可以说是学的差不多了),自我感觉良好,便想找道题练练,于是,打着写最短路的招牌,找到了这道题。
诶?,这道题怎么这么不对劲啊,想了半天,想了一个思路:既然他的路径上是关系运算符,且这是一个有向图,就说明他的节点是有着一个先后顺序的,想到这些,我决定打暴力:即在输入时按照关系运算符将节点编号排序,然后,将排在第一位的节点的值赋为0,然后递推下去,输出最后节点的值,。。。。。。。。。。。。。。。。。但是失败了,因为我无法处理相同关系的节点。。。。。。。。。。。。。。
所以,只好上网查题解,于是看到了一个从未见过的名词:差分约束系统!!!!!!!!!
3.题解
好吧,总之写一点题解吧(虽然我也不太懂),
我们可以考虑如果输入x,y,z,
若z== 1 , 那么x到y连一条权值1的边;
z== -1 ,y到x连一条权值1的边;
z==0 , 则两个点连一条权值0的无向边就好了;
代码如下(这是糖果一题中的不等关系,囊括了所有的不等关系)
for (int i=1;i<=m;++i)
{
int z=read(),x=read(),y=read();
if (z==1) add(x,y,0),add(y,x,0);
//z=1,表示第A个小朋友分到的糖果必须和第B个小朋友分到的糖果一样多
else if (z==2)
{//z=2, 表示第A个小朋友分到的糖果必须少于第B个小朋友分到的糖果
if (x==y) { puts("-1"); exit(0); }
add(x,y,1);
}
else if (z==3) add(y,x,0);
//z=3,表示第A个小朋友分到的糖果必须不少于第B个小朋友分到的糖果
else if (z==4)
{//z=4,表示第A个小朋友分到的糖果必须多于第B个小朋友分到的糖果
if (x==y) { puts("-1"); exit(0); }
add(y,x,1);
}
else add(x,y,0);
//z=5,表示第A个小朋友分到的糖果必须不多于第B个小朋友分到的糖果
}
那么我们建立好了这个差分系统时,
2.我们就可以跑一边最长路了,
因为是最长路,
所以 dist[数组 ]初始化要是负无穷大,
和最短路一样但是是对立的,
如果有一个正权环 , 那么我们肯定可以沿着这条正权环绕啊绕得到更长的最长路,
所以 不存在正确的k时就是不存在最长路的情况,
即比如a到b权1(a比b大) ,
b到c权1(b比c大),
而c到a也权1(c比a大) 自然是无解,
所以我们就跑 SPFA最长路+判负环就好了,
然后就是我们可以看到,
SPFA是求单源最短路径,
而这道题并没有明确的起点终点,
所以要得到每个点的d[]取最大值了,我们该怎么做到呢?
这里有一个小技巧了,
我们设置一个虚的源点0,
然后再从0到每个顶点连一条权值为0的有向边,
代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=1050;
const int maxm=20005;
const int inf=0x7fffffff;
struct rec
{
int y,z,Next;
rec()
{
y=z=Next=-1;
}
}edge[maxm];
int n, m, tot, ans, head[maxn], cnt[maxn];
inline int read()
{
int f=1,num=0;
char ch=getchar();
while (ch<'0'||ch>'9') { if (ch=='-') f=-1; ch=getchar(); }
while (ch>='0'&&ch<='9') num=(num<<1)+(num<<3)+ch-'0', ch=getchar();
return num*f;
}
void add(int x,int y,int z)
{
edge[++tot].y=y,edge[tot].z=z,edge[tot].Next=head[x],head[x]=tot;
}
bool v[maxn];
int dist[maxn];
queue<int>q;
void spfa(int s)
{
for (int i=1;i<=n;i++) dist[i]=-inf;
memset(v,0,sizeof(v));
dist[s]=0,v[s]=1;
q.push(s);
cnt[s]++;
while (!q.empty())
{
int x=q.front();
q.pop(),v[x]=0;
for (int i=head[x];i!=-1;i=edge[i].Next)
{
int y=edge[i].y,z=edge[i].z;
if (dist[y]<dist[x]+z)//求最长路
{
dist[y]=dist[x]+z;
if (!v[y])
{
q.push(y),v[y]=1;
if (++cnt[y]>n)
{
cout<<"NO"<<endl;
exit(0);
}
}
}
}
}
for (int i=1;i<=n;i++)
ans=max(ans,dist[i]);
cout<<ans<<endl;
}
int main()
{
memset(head,-1,sizeof(head));
n=read(),m=read();
int x,y,z;
for (int i=1;i<=m;i++)
{
x=read(),y=read(),z=read();
if (z==-1) add(y,x,1);
else if (z==1) add(x,y,1);
else if (z==0) add(x,y,0),add(y,x,0);//即构建一个差分约束系统
}
for (int i=1;i<=n;i++)
add(0,i,0);//虚设源点
spfa(0);
return 0;
}
ok啦,这道题就解决了!!!!!!!!!