差分约束
什么是差分约束问题?
简单来说差分约束给出k个不等式 形如: x i < = x j + c ( i ! = j , c 为 常 数 ) xi<=xj+c(i!=j,c为常数) xi<=xj+c(i!=j,c为常数)求出一个可行解 x 1 = a 1..... x k = a k x1=a1.....xk=ak x1=a1.....xk=ak
具体的题目:
幼儿园里有 N 个小朋友,老师现在想要给这些小朋友们分配糖果,要求每个小朋友都要分到糖果。
但是小朋友们也有嫉妒心,总是会提出一些要求,比如小明不希望小红分到的糖果比他的多,于是在分配糖果的时候, 老师需要满足小朋友们的 K 个要求。
幼儿园的糖果总是有限的,老师想知道他至少需要准备多少个糖果,才能使得每个小朋友都能够分到糖果,并且满足小朋友们所有的要求。
输入格式
输入的第一行是两个整数 N,K。
接下来 K 行,表示分配糖果时需要满足的关系,每行 3 个数字 X,A,B。
如果 X=1.表示第 A 个小朋友分到的糖果必须和第 B 个小朋友分到的糖果一样多。
如果 X=2,表示第 A 个小朋友分到的糖果必须少于第 B 个小朋友分到的糖果。
如果 X=3,表示第 A 个小朋友分到的糖果必须不少于第 B 个小朋友分到的糖果。
如果 X=4,表示第 A 个小朋友分到的糖果必须多于第 B 个小朋友分到的糖果。
如果 X=5,表示第 A 个小朋友分到的糖果必须不多于第 B 个小朋友分到的糖果。
小朋友编号从 1 到 N。
输出格式
输出一行,表示老师至少需要准备的糖果数,如果不能满足小朋友们的所有要求,就输出 −1。
用
x
A
表
示
A
同
学
获
得
的
糖
果
数
,
同
理
用
x
B
表
示
B
同
学
获
得
的
糖
果
数
x_{A}表示A同学获得的糖果数,同理用x_{B}表示B同学获得的糖果数
xA表示A同学获得的糖果数,同理用xB表示B同学获得的糖果数
上述题目给出k个约束
X
=
1
时
,
x
A
>
=
x
B
+
0
,
x
B
>
=
x
A
+
0
X=1时,x_{A}>=x_{B}+0,x_{B}>=x_{A}+0
X=1时,xA>=xB+0,xB>=xA+0
X
=
2
时
,
x
B
>
=
x
A
+
1
X=2时,x_{B}>=x_{A}+1
X=2时,xB>=xA+1
X
=
3
时
,
x
A
>
=
x
B
+
0
X=3时,x_{A}>=x_{B}+0
X=3时,xA>=xB+0
X
=
4
时
,
x
A
>
=
x
B
+
1
X=4时,x_{A}>=x_{B}+1
X=4时,xA>=xB+1
X
=
5
时
,
x
B
>
=
x
A
+
0
X=5时,x_{B}>=x_{A}+0
X=5时,xB>=xA+0
答案就是出最小的可行解的和。
差分约束问题算法
对于每个不等式,我们可以看作是图论中最短路的一条边。
对约束的一般形式
x
i
<
=
x
j
+
c
x_{i}<=x_{j}+c
xi<=xj+c则可以等价于最短路中,d[i]<=d[j]+c;其中c是j->i的一条边的权值
反证法证明:
若d[i]为源点到i的最短路径,
但d[i]>d[j]+c,则可以通过d[j]+c来优化最短路则d[i]不为最短路径,与条件矛盾。
所以差分约束的一个可行解就是源点到各点最短路径。
差分约束可行解若存在,则最短路存在,反之同理,所以如果存在负环,那么差分约束可行解不存在。
具体做法:
1.将每个不等式转化为一条边,进行建图。
2.选择一个源点,源点需要满足:能够遍历所有的边(满足所有不等式)
差分约束最值问题
通过上面的讨论我们可以得出一个差分约束的可行解,但题目通常会问差分约束最值。例如这一道题,需要求的是最小值。
我们先从最大值进行讨论,最小值则为对偶问题。
假定题目有:
x
i
<
=
x
j
+
c
j
,
x
j
<
=
x
k
+
c
k
,
x
k
<
=
c
z
x_{i}<=x_{j}+c{j} , x_{j}<=x_{k}+c_{k}, x_{k}<=c_{z}
xi<=xj+cj,xj<=xk+ck,xk<=cz
通过放缩我们可以得到
x
i
<
=
x
j
+
c
j
<
=
x
k
+
c
k
+
c
j
<
=
c
j
+
c
k
+
c
z
x_{i}<=x_{j}+c_{j}<=x_{k}+c_{k}+c{j}<=c_{j}+c_{k}+c_{z}
xi<=xj+cj<=xk+ck+cj<=cj+ck+cz
则得到
x
i
x_{i}
xi一个上界,这里需要注意到,若最终链式不等式最右端仍有变量,则无最值,因为将等式两边同时加一个常数并不会影响不等式,所以最值存在一定存在某个限制条件使得最右端都为常数。
我们可以发现这样对应的一个链式反应其实对应着一条最短路径上面一个样例为(i->k),这样我们得到一个上界,从i出发会有不止一个上界。最终我们会得到关于
x
i
的
一
系
列
约
束
比
如
:
x
i
<
=
5
,
x
i
<
=
4
,
x
i
<
=
1
x_{i}的一系列约束比如: x_{i}<=5,x_{i}<=4,xi<=1
xi的一系列约束比如:xi<=5,xi<=4,xi<=1通过简单的数学知识我们可以知道,这样一系列约束,xi的最大值只能取1.也就是上界的最小值所以最短路求得是最大值。
同理可证:最小值为下界的最大值,那么就是最长路。
针对最值的算法步骤:
最大值
将约束条件转换为形如
x
i
<
=
x
j
+
c
x_{i}<=x_{j}+c
xi<=xj+c
对应从j->i有一条边权为c的 边,然后跑一遍最短路算法。
最小值将约束条件转换为形如
x
i
>
=
x
j
+
c
x_{i}>=x_{j}+c
xi>=xj+c
对应从j->i有一条边权为c的 边,然后跑一遍最长路算法。
本题就是需要跑一遍最长路算法。
特殊的判断(环)
1.环的判断,如果一条路径上的点超过了n+1,说明存在环。
其中最长路中存在正环无解,最短路中存在负环无解。
2.trick
将spfa中的队列换成栈可以优化判断环的速度。本题队列过不了,栈能过。
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10,M=1e6;
typedef long long ll;
int h[N],w[M],e[M],ne[M],idx;//邻接表
int st[N],cnt[N];//前者记录点的状态,后者记录路径中点的个数。
ll d[N];
int n,m;
void add(int a,int b,int c)
{
e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
int spfa()
{
stack<int>s;
s.push(0);
memset(d,-0x3f,sizeof d);
d[0]=0;
st[0]=1;
while(s.size())
{
int tt=s.top();s.pop();
st[tt]=0;
for(int i=h[tt];i!=-1;i=ne[i])
{
int j=e[i];
if(d[j]<d[tt]+w[i])
{
cnt[j]=cnt[tt]+1;
if(cnt[j]>=n+1)return 0;
d[j]=d[tt]+w[i];
if(!st[j])s.push(j),st[j]=1;
}
}
}
return 1;
}
int main()
{
memset(h,-1,sizeof h);
cin>>n>>m;
for(int i=1;i<=n;i++)add(0,i,1);
for(int i=1;i<=m;i++)
{
int k,a,b,c;
cin>>k>>a>>b;
if(k==1)add(a,b,0),add(b,a,0);
else if(k==2)add(a,b,1);
else if(k==3)add(b,a,0);
else if(k==4)add(b,a,1);
else add(a,b,0);
}
if (spfa())
{
ll res=0;
for(int i=1;i<=n;i++)
res+=d[i];
cout<<res<<endl;
}
else cout<<-1<<endl;
}