概念:已知一个差分约束系统(差分约束系统即一种特殊的n元一次不等式组),形如,要求求出是否存在一组解使得所有约束条件满足。由于在最短路中与该不等式形式相似,因此,可以利用图论,从对应的j点连向i点,并且对应构建出的图,采用bellmanford算法解决带负权负环的问题。
-
判断解的存在性
-
存在负环说明无解;
-
不存在由s到t的路径说明s与t之间不存在约束条件
差分约束系统的规范化:
-
若仅存在<=关系,本质是求解最短路,若仅存在>=关系,本质是求解最长路
-
如果存在a-b=c的情况则差分约束条件转化为a-b>=c且a-b<=c
-
如果变量都是整数意义上的,则a-b<c请务必转化为a-b<=c-1,保证约束系统的规范性方便建图
-
若<=和>=关系同时存在,题干若问两点差值最小,相当于求解的最小值,此时相当于求解最长路,将差分约束条件转化为>=关系(两边同乘-1)即可;同理,否则求解最短路。
-
尤其注意不等关系的隐含
差分约束的应用
-
线性约束
-
【模板】差分约束 - 洛谷
思路:这题唯一的不同之处是要求求出可行的一组解,易知任意赋予某个点一个初始的权值,那么可以给与该点相连的其他点赋上’该点权值加上最短边权‘的总和值,但若有些点无法从该点到达则会很别扭,只能赋给它我们初始化的无穷值。因此,不妨建立一个超级源点连接每一条边,既能严谨的判断负环的存在性,也能更好的给出一组解。
-
有意思的是,如果将超级源点到其他各点的边权设为0,同时超级源点的初始值设为0,则求出的一组解都一定是小于等于0的,因为只有更短的负权路径去更新超级源点到各点的距离,因此求出解非正。而且跟着边权赋值得出一组解的,最终一定都是临界值。若是<=不等式应该是最大值,反之则是最小值
懒得画图详解了,可看算法学习笔记(11): 差分约束
代码实现:
#include<bits/stdc++.h>
#define maxn 10000
#define INF 0x3f3f3f3f
#define ll long long
using namespace std;
int n,m;
int u[maxn],v[maxn];
ll w[maxn],dis[maxn];
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
scanf("%d%d%lld",&v[i],&u[i],&w[i]);
for(int i=1;i<=n;i++)
{
u[i+m]=0;
v[i+m]=i;
w[i+m]=1000;//设边权为1000
}//建立超级源点
memset(dis,0x3f,sizeof(dis));
dis[0]=0;
for(int k=1;k<=n;k++)//因为有n+1个点
for(int i=1;i<=m+n;i++)
dis[v[i]]=min(dis[u[i]]+w[i],dis[v[i]]);
int flag=0;
for(int i=1;i<=m+n;i++)
if(dis[v[i]]>dis[u[i]]+w[i])
flag=1;
if(flag)
printf("NO");
else{
for(int i=1;i<=n;i++)
printf("%lld ",dis[i]);//注意,跟着边权赋值,最终一定都是临界值
}
system("pause");
}
-
区间约束
-
种树 - 洛谷
-
思路:利用差分思想,假设d[i]-d[j-1]就是区间上[j,i]种的树的棵数,那么原问题就被转化成了一系列的>=不等式组,构成了差分约束系统。同时还需关注这道题的隐含条件---1>=d[i]-d[i-1]>=0,原因是这是一个整型氛围,需要进行相应的约束。
-
同时bellman超时,选用spfa大大优化时间复杂度。
#include<bits/stdc++.h>
#include<queue>
#define maxn 100000
#define INF 0x3f3f3f3f
#define ll long long
using namespace std;
int n,m;
ll tot=0,dis[maxn],head[maxn];
struct Edge{
int to;
int next;
ll w;
}edge[maxn];
void addedge(int u,int v,ll w){
edge[++tot].to=v;
edge[tot].next=head[u];
edge[tot].w=w;
head[u]=tot;
}
void spfa(void)
{
ll vis[maxn],s;
queue<ll>q;
vis[0]=1;
q.push(0);
while(!q.empty())
{
s=q.front();
q.pop();
vis[s]=0;
for(ll i=head[s];~i;i=edge[i].next)
{
ll to=edge[i].to;
if(dis[to]>dis[s]+edge[i].w)
{
dis[to]=dis[s]+edge[i].w;
if(!vis[s])vis[to]=1,q.push(to);
}
}
}
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=0;i<=n;i++)
head[i]=-1;
for(int i=1;i<=m;i++)
{
int u,v;
ll w;
scanf("%d%d%lld",&u,&v,&w);
addedge(u-1,v,-w);
}//求最长路
for(int i=1;i<=n;i++)
{
addedge(i,i-1,1);
addedge(i-1,i,0);
}
memset(dis,0x3f,sizeof(dis));
dis[0]=0;
spfa();
printf("%lld",-dis[n]);
system("pause");
}