差分约束问题
差分约束系统
给出一些形如x-y<=b不等式的约束,问是否有满足问题的解,或者求最小,最大解。
这个问题的神奇之处是可以转化为图论的最短路问题。
原理
对于图论的最短路径,有:对于d(v) <= d(u) + w(u, v) ,而差分约束系统的解法利用到了单源最短路径问题中的三角形不等式。
移项得:d(v) – d(u) <= w(u, v),是不是和上面的x-y<=b的一样?
是的,这就是转化为最短路径算法的原理。
建图
1.求最小解
有一个序列,题目用n个整数组合 [ai,bi,ci]来描述它,[ai,bi,ci]表示在该序列中处于[ai,bi]这个区间的整数至少有ci个。如果存在这样的序列,请求出满足题目要求的最短的序列长度是多少。
我们可以设s[i]为从[1,i]闭区间内的整数个数。那么显然对于组合[a,b,c] 我们有 s[b] – s[a-1] >= c (a-1是因为我们需要算上a)还有:0<= s[b]-s[b-1] <=1
变形得
S[b]-S[b-1] >=0
S[b-1]-S[b] >=-1
接着,对于求最小值的来说,我们用最长路来求解。为什么是最长路?一开始把距离d设为无穷小,之后最长路的更新公式为:if(d[v] < d[u]+w(u,v)) d[v]=d[u]+w(u,v); 可以看到d[v]不断的增大,即当满足条件的时候为最小的解。
所以我们在建图的时候,需要满足最长路的三角不等式: d(v) >= d(u) + w(u, v) =>d(v) – d(u) >=w(u,v) 的形式=>从u到v连接一条权重为w(u,v)的边。找出不等关系解出即可。
2.求最大解
一些母牛按升序排成一条直线。有两种要求,A和B距离(A<B)不得超过X,还有一种是C和D(C>D)距离不得少于Y,问可能的最大距离。如果没有输出-1,如果可以随便排输出-2,否则输出最大的距离。
题目按升序排,所以有:
s[i] – s[i-1] >=0
由题目的约束条件得到:
B – A <= X
D – C >= Y
因为题目求的是最大距离,故用最短路来求解
最短路三角不等式为 d(v) <= d(u) + w(u,v) => d(v) – d(u) <= w(u,v) =>从u到v连接一条权重为w(u,v)的边
所以我们都转化为小于等于的符号,所以有:
s[i-1] – s[i] <=0
B – A <= X
C – D <= -Y
建完图后SPFA即可。(有负环说明无解输出-1 , 1与n不连通说明可以随意摆放,没有约束嘛。输出-2,否则输出dis [n])
题目
poj3159:求最大解
这个没什么好说的,唯一想吐槽的是队列SPFA要T而栈式SPFA可以过,还有就是poj scanf比读入优化快。
zoj1770:求最小解(SPFA判环)
同上,判负环条件:一个点入队超过n次。
poj2983:判断是否可行(图不一定连通)
加入源点s向每一个点连接权值为0的边,之后判负环。
#include<iostream>
#include<cstring>
#include<queue>
using namespace std;
const int Maxn=1e3+50,Maxm=5e5+50;
int n,m,dis[Maxn],last[Maxn],to[Maxm],val[Maxm],before[Maxm],in[Maxn],vis[Maxn],ecnt=1;
queue<int>q;
char ch[20];
inline void add(int x,int y,int c)
{
before[++ecnt]=last[x];
last[x]=ecnt;
to[ecnt]=y;
val[ecnt]=c;
}
int a,b,x;
int main()
{
while(scanf("%d%d",&n,&m)!=EOF)
{
ecnt=1;
memset(in,0,sizeof(in));
memset(last,0,sizeof(last));
memset(dis,0x3f,sizeof(dis));
int bz=1;
for(int i=1;i<=m;i++)
{
scanf("%s",ch+1);
if(ch[1]=='P')
{
scanf("%d%d%d",&a,&b,&x);
add(b,a,x);add(a,b,-x);
if(a==b&&x!=0)bz=0;
}
else
{
scanf("%d%d",&a,&b);
add(a,b,-1);
if(a==b)bz=0;
}
}
for(int i=1;i<=n;i++)add(n+1,i,0);
dis[n+1]=0;q.push(n+1),vis[n+1]=1;
while(!q.empty())
{
int u=q.front();q.pop();vis[u]=0;
for(int e=last[u];e&&bz;e=before[e])
{
if(dis[to[e]]>dis[u]+val[e])
{
dis[to[e]]=dis[u]+val[e];
if(!vis[to[e]])
{
in[to[e]]++;
if(in[to[e]]>n)bz=0;
dis[to[e]]=dis[u]+val[e];
q.push(to[e]);
}
}
}
}
if(bz)puts("Reliable");
else puts("Unreliable");
}
}
poj1275:二分判断解性
主要是将总人数a二分并添加条件:sum≥a.
poj1364: 判断合法性
两个坑点:1.将大于小于转化为大等于小等于。2.没有s[i]-s[i-1]≥0这个条件。