注意:差分约束系统
如果一个系统由n个变量和m个不等式组成,形如 Xj-Xi<=b*k(i,j属于[1,n],k属于[1,m] ), 这样的系统称之为差分约束系统。差分约束系统通常用于求关于一组变量的不等式组。
求解差分约束系统可以转化为图论中单源最短路问题。
Xj-Xi<=k ==> d[v]-d[u]<=w[u,v] ( 这里是我们要根据约束条件求的结果,尽量往它靠近) ==> 所以转化为图论求解 ,也就是if(d[v]-d[u] >w[u,v]) 那么 d[v]-d[u]<=w[u,v] 。 路径距离初始化 dis[i]=INF
Xj-Xi>=k ==> d[v]-d[u]>=w[u,v] ( 这里是我们要根据约束条件求的结果,尽量往它靠近) ==> 所以转化为图论求解 ,也就是if(d[v]-d[u] <w[u,v]) 那么 d[v]-d[u]>=w[u,v] 。
路径距离初始化 dis[i]=-INF
再增加一个源点s,源点到所有定点的距离为0( 添加源点的实质意义为默认另外一系列的不等式组Xi-Xo<=0),再对源点利用spfa算法。
注意几个问题:
1、当0没有被利用的时候,0作为超级源点。当0已经被利用了,将n+1(未被利用)置为超级源点。
2、对于Xj-Xi=k 可以转换为 Xj-Xi<=k Xj-Xi>=k来处理。
3、a.若要判断图中是否出现负环,可以利用深度优先搜索。以前利用spfa是这样的(head->****->tail),当head和tail之间所有点都遍历完了才轮得上tail这个点,这样的话我们无法判断图中有没有负环,我们可以这样改变一样遍历顺序,head->tail->***->head。 当深度优先搜索过程中下次再次遇见head(也就是head节点依然在标记栈中)时,则可判断图中含有负环,否则没有。
b.也可以判断某个点被访问超过n次就是存在负权环了
注意负值的取法。
4、当图连通时,只需要对源点spfa一次;当图不连通时,对每个定点spfa一次。
5、对于 Xj-Xi<k or Xj-Xi>k , 差分约束系统只针对>= or <=, 所以我们还要进行巧妙转换编程大于等于小于等于。
6. 对于A-x==B 问题需要转换为 A—B>=x &&A-B<=x 也就是Build(A,B,-x),Build(B,A,x)建立两次
POJ 2983
题意:给出两种关于防御站位置的信息,一种是确切的信息,P A B X,表示a在b北面x距离的地方,另一种是V A B,表示只知道A在B的北面。问这些信息有没有矛盾。
分析:差分约束。第一种记为A-B>=X&&A-B<=X,第二种记为A-B>=0,用SPFA求得解,则没有矛盾,否则就有矛盾。
方程一: //倒着建图(代码正好与正向相反)
不等式方程 dis[a]>=dis[b]+x 是正常条件
此时需要将dis[i]为-INF;
当dis[a]<dis[b]+x 是需要更新dis[a]的。
Build(A,B,x),Build(B,A,-x);
方程二 //正着建图
dis[a]-x>=dis[b]
dis[i]初始化为INF;
当dis[b]>dis[a]-x时 需要更新dis[b];
Build(A,B,-x),Build(B,A,x);
对于相等的条件是 A-B>=X&&A-B<=X 也就是将相等的条件建成
Build(A,B,-x),Build(B,A,x);
看是否有负权环,形成矛盾。
用栈超时,用队列或优先队列可过
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <queue>
#include <vector>
#include <stack>
#include <map>
#include <algorithm>
#include <cmath>
#include <ctime>
#define LL long long
#define Pr pair<int,int>
#define bug puts("*********")
#define fread(ch) freopen(ch,"r",stdin)
#define fwrite(ch) freopen(ch,"w",stdout)
using namespace std;
const int INF = 0x3f3f3f3f;
const double eps = 1e-8;
const int mod = 1e9+7;
const int N = 212345;
int num=0;
int sta[N];
int head[N];
int dis[N];
int vis[N];
int used[N];
int n,m;
struct node{
int to;
int val;
int next;
}p[N];
void Build(int x,int y,int val){
p[num].to=y;
p[num].val=val;
p[num].next=head[x];
head[x]=num++;
}
void SPFA(){
memset(dis,INF,sizeof(dis));
memset(vis,0,sizeof(vis));
memset(used,0,sizeof(used));
//queue<int>Q;
priority_queue<int>Q;
int top=0;
//sta[++top]=0;
Q.push(0);
vis[0]=1;
dis[0]=0;
while(!Q.empty()){
// int x=sta[top--];
//vis[x]=0;
int x=Q.top();
Q.pop();
vis[x]=0;
for(int i=head[x];i!=-1;i=p[i].next){
int to=p[i].to;
if(dis[to]>dis[x]+p[i].val){
dis[to]=dis[x]+p[i].val;
if(!vis[to]){
vis[to]=1;
used[to]++;
//sta[++top]=to;
Q.push(to);
if(used[to]>n){
puts("Unreliable");
return;
}
}
}
}
}
puts("Reliable");
}
char s[10];
int main(){
int u,v,z;
while(~scanf("%d%d",&n,&m)){
memset(head,-1,sizeof(head));
num=0;
for(int i=1;i<=m;i++){
scanf("%s",s);
if(s[0]=='P'){
scanf("%d%d%d",&u,&v,&z);
Build(v,u,z);
Build(u,v,-1*z);
}
else{
scanf("%d%d",&u,&v);
Build(u,v,-1);
}
}
for(int i=1;i<=n;i++){
Build(0,i,0);
}
SPFA();
}
return 0;
}
求最小的解
POJ1201
题目说[ai, bi]区间内和点集Z至少有ci个共同元素,那也就是说如果我用Si表示区间[0,i]区间内至少有多少个元素的话,那么Sbi - Sai >= ci,这样我们就构造出来了一系列边,权值为ci,但是这远远不够,因为有很多点依然没有相连接起来(也就是从起点可能根本就还没有到终点的路线),此时,我们再看看Si的定义,也不难写出0<=Si - Si-1<=1的限制条件,虽然看上去是没有什么意义的条件,但是如果你也把它构造出一系列的边的话,这样从起点到终点的最短路也就顺理成章的出现了。
那么显然对于组合[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)的边。
故本题中三个不等式都是如下的形式:
- s[b] – s[a-1] >= c
- S[b]-S[b-1] >=0
- S[b-1]-S[b] >=-1
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<queue>
#include<set>
#include<map>
#define INF 0x3f3f3f3f
#define PI acos(-1)
#define eps 0.00000001
#define LL long long
using namespace std;
const int MM=150010;
const int NN=50010;
struct node{
int to,val,next;
}p[MM];
int num=0,n,m;
int head[NN];
int dis[NN];
int vis[NN];
int Max,Min;
void Build(int x,int y,int z){
p[num].to=y;
p[num].val=z;
p[num].next=head[x];
head[x]=num++;
}
int sta[MM];
void spfa(){
//memset(sta,0,sizeof(sta));
memset(dis,-INF,sizeof(dis));
memset(vis,0,sizeof(vis));
// cout<<Min<<endl;
dis[Min]=0;
// cout<<dis[2]<<endl;
vis[Min]=1;
int top=0;
sta[++top]=Min;
while(top){
int x=sta[top];
top--;
vis[x]=0;
for(int i=head[x];i!=-1;i=p[i].next){
int to=p[i].to;
if(dis[x]+p[i].val>dis[to]){
dis[to]=dis[x]+p[i].val;
if(!vis[to]){
vis[to]=1;
sta[++top]=to;
}
}
}
}
//cout<<Max<<endl;
printf("%d\n",dis[Max]);
}
int main(){
while(~scanf("%d",&n)){
num=0;
Max=0;
Min=INF;
memset(head,-1,sizeof(head));
for(int i=0;i<n;i++){
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
Max=max(Max,b+1);
Min=min(Min,a);
Build(a,b+1,c);
}
for(int i=Min;i<=Max;i++){
Build(i,i+1,0);
Build(i+1,i,-1);
}
spfa();
}
return 0;
}
POJ3159
班上有n个同学,现在有一些糖要分给他们,设第i个同学得到的糖为p[i],分糖必须满足条件:第i个同学要求第j个同学的糖不能超过自己k个,即p[j] - p[i] <= k,k >= 0。要求在满足这些条件的情况下,求出p[n] - p[1]的最大值。
不妨将糖果数当作距离,把相差的最大糖果数看成有向边AB的权值,我们得到 dis[B]-dis[A]<=w(A,B)。看到这里,我们联想到求最短路时的松弛技术,即if(dis[B]>dis[A]+w(A,B), dis[B]=dis[A]+w(A,B)。即是满足题中的条件dis[B]-dis[A]<=w(A,B),由于要使dis[B] 最大,所以这题可以转化为最短路来求。这题如果用SPFA 算法的话,则需要注意不能用spfa+queue 来求,会TLE ,而是用 spfa + stack
用优先 队列也行
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<queue>
#include<set>
#include<map>
#define INF 0x3f3f3f3f
#define PI acos(-1)
#define eps 0.00000001
#define LL long long
using namespace std;
const int MM=150010;
const int NN=30010;
struct node{
int to,val,next;
}p[MM];
int num=0,n,m;
int head[NN];
int dis[NN];
int vis[NN];
void Build(int x,int y,int z){
p[num].to=y;
p[num].val=z;
p[num].next=head[x];
head[x]=num++;
}
int sta[MM];
void spfa(){
//memset(sta,0,sizeof(sta));
memset(dis,INF,sizeof(dis));
memset(vis,0,sizeof(vis));
dis[1]=0;
// cout<<dis[2]<<endl;
vis[1]=1;
int top=0;
sta[++top]=1;
while(top){
int x=sta[top];
top--;
vis[x]=0;
for(int i=head[x];i!=-1;i=p[i].next){
int to=p[i].to;
if(dis[x]+p[i].val<dis[to]){
dis[to]=dis[x]+p[i].val;
if(!vis[to]){
vis[to]=1;
sta[++top]=to;
}
}
}
}
printf("%d\n",dis[n]);
}
int main(){
while(~scanf("%d%d",&n,&m)){
num=0;
memset(head,-1,sizeof(head));
for(int i=0;i<m;i++){
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
Build(a,b,c);
}
spfa();
}
return 0;
}
POJ 1364
题目大意;已知一个序列a[1], a[2], ......, a[n],给出它的若干子序列以及对该子序列的
约束条件,例如a[si], a[si+1], a[si+2], ......, a[si+ni],且a[si]+a[si+1]
+a[si+2]+......+a[si+ni] < or > ki。求是否存在满足以上m个要求的数列。是
则输出“lamentable kingdom”,否则输出“successful conspiracy”。
解题思路:
s[a] + s[a+1] + …… + s[b] < c 可以转化成前n项和sum[b] - sum[a - 1] < c,
为了能用Bellman_Ford,即将< 转化成 <= ,sum[b] - sum[a - 1] <= c - 1。
转化为最长路或者最短路来判断是否有解都可以。
解题感想;
注意题目求约束的最大,或者最小时,就要判断此刻要用的是最短路,还是最长路。
要构建超级源点。就是为了让所有的点都可以被录入的队列中去。
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<cstdio>
#include<queue>
#include<map>
#define bug puts("**********");
#define INF 0x3f3f3f3f
#define LL long long
using namespace std;
const int N=200;
char op[10];
int head[N*N];
int vis[N];
int used[N];
int dis[N],n;
struct node{
int to;
int val;
int next;
}p[N*N];
int num=0;
void Build(int u,int v,int d){
p[num].to=v;
p[num].val=d;
p[num].next=head[u];
head[u]=num++;
}
queue<int>Q;
void spfa(){
while(!Q.empty()) Q.pop();
for(int i=1;i<=n+2;i++) dis[i]=INF;
memset(vis,0,sizeof(vis));
memset(used,0,sizeof(used));
vis[0]=1;
dis[0]=0;
Q.push(0);
used[0]++;
while(!Q.empty()){
int x=Q.front();
Q.pop();
vis[x]=0;
for(int i=head[x];i!=-1;i=p[i].next){
int to=p[i].to;
if(dis[x]+p[i].val<dis[to]){
dis[to]=dis[x]+p[i].val;
if(!vis[to]){
vis[to]=1;
Q.push(to);
used[to]++;
if(used[to]>n+2){
puts("successful conspiracy");
return ;
}
}
}
}
}
puts("lamentable kingdom");
}
int main(){
int u,v,d,m;
while(~scanf("%d",&n),n){
memset(head,-1,sizeof(head));
num=0;
scanf("%d",&m);
for(int i=0;i<m;i++){
scanf("%d%d%s%d",&u,&v,op,&d);
if(op[0]=='g')
Build(u+v+1,u,-d-1); ///不能减1 是因为会与超级源点重复
else
Build(u,u+v+1,d-1);
}
for(int i=1;i<=n+1;i++){ ///超级源点的构建
Build(0,i,0);
}
spfa();
}
return 0;
}