差分约束简单来说是一种把不等式转变成图的一种思想。因为题目会给你很多不等式,使用数学方法很有可能无解,而且难以找到这些不等式之间的联系。但是当我们把它们转化成图之后,就可以使用图论的思想来解题。比如说 a−b<=c a − b <= c ,我们就把b向a连一条边权为 c c 的边。然后我们通过最短路求与 b b 差的最大值。同理,我们就把 b b 向连一条边权为 c c 的边。然后我们通过最长路求与 b b 差的最小值。
差分约束遵循以下几种原则:
1、 —— b b 向连一条边权为 c c 的边,最短路求解与 b b 差的最大值,若有负权环则证明无解
2、 —— b b 向连一条边权为 c c 的边,最长路求解与 b b 差的最小值,若有正权环则证明无解
3、= b b —— 和 b b 之间互相连一条无向边,边权为0。
4、如果出现或 > > 并且有整数限制,可以连边或 c−1 c − 1 。
5、如果求不出最短路或者最长路,则是任意解。
下面出一道例题:
小 K 在 Minecraft 里面建立很多很多的农场,总共
n
n
个,以至于他自己都忘记了每个农场中种植作物的具体数量了,他只记得一些含糊的信息(共 个),以下列三种形式描述:
农场
a
a
比农场 至少多种植了
c
c
个单位的作物。
农场 比农场
b
b
至多多种植了 个单位的作物。
农场
a
a
与农场 种植的作物数一样多。
但是,由于小 K 的记忆有些偏差,所以他想要知道存不存在一种情况,使得农场的种植作物数量与他记忆中的所有信息吻合。
输入格式:
从 farm.in 中输入数据
第一行包括两个整数
n
n
和 ,分别表示农场数目和小 K 记忆中的信息数目。
接下来
m
m
行:
如果每行的第一个数是 1,接下来有 3 个整数 ,表示农场
a
a
比农场 至少多种植了
c
c
个单位的作物。
如果每行的第一个数是 2,接下来有 3 个整数 ,表示农场
a
a
比农场 至多多种植了
c
c
个单位的作物。如果每行的第一个数是 3,家下来有 2 个整数 ,表示农场
a
a
终止的数量和 一样多。
输出格式:
输出到 farm.out 中
如果存在某种情况与小 K 的记忆吻合,输出“Yes”,否则输出“No”。
输入样例#1:
3 3
3 1 2
1 1 3 1
2 2 3 2
输出样例#1:
Yes
对于 100% 的数据保证: 1≤n,m,a,b,c≤10000 1 ≤ n , m , a , b , c ≤ 10000 。
小K的农场
解法:
这道题是裸的差分约束。
1、
a=>b+c
a
=>
b
+
c
b−a<−c
b
−
a
<
−
c
(
a
a
向连一条权值为
−c
−
c
的边)
2、
a<=b+c
a
<=
b
+
c
a−b<=c
a
−
b
<=
c
(
b
b
向连一条权值为
c
c
的边)
3、
a−b<=0,b−a<=0
a
−
b
<=
0
,
b
−
a
<=
0
(a,b连一条权值为0的无向边)
如果出现负环,则无解。
然后说一下判负环:
1、spfa如果一个点进队列
>n
>
n
次,则证明出现负环。
2、对于1的优化,如果一个点的最短路径上有
>n
>
n
条边,则证明有负环。
3、对于专门判环的题,可以把spfa写成dfs版本。如果一个点进队两次则证明有负环。(具体代码如下)
吐槽一下洛谷的水数据,前两种方法根本过不去啊,只有第三个才行而且还4ms==。
Bfs(翻车炸掉了):
void spfa(int u)
{
queue <int> d;
dis[u]=0;
d.push(u);
vis[u]=1;
while(!d.empty())
{
int now=d.front();
d.pop();
vis[now]=0;
for(int i=head[now];i!=-1;i=b[i].next)
{
if(dis[now]+b[i].len<dis[b[i].to])
{
dis[b[i].to]=dis[now]+b[i].len;
cnt_[b[i].to]=cnt_[now]+1;
if(vis[b[i].to]==0)
{
d.push(b[i].to);
vis[b[i].to]=1;
}
if(cnt_[b[i].to]>n)
{
printf("No");
exit(0);
}
}
}
}
}
dfs(没炸):
bool spfa(int u)
{
vis[u]=1;
for(int i=head[u];i!=-1;i=b[i].next)
{
if(dis[b[i].to]>dis[u]+b[i].len)
{
if(vis[b[i].to]==1)
return false;
dis[b[i].to]=dis[u]+b[i].len;
if(!spfa(b[i].to))
return false;
}
}
vis[u]=0;
return true;
}
最后贴一下题目的代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<queue>
using namespace std;
struct lxy{
int to,next,len;
}b[40005];
int n,m,head[10005],cnt=0;
int dis[10005];
bool vis[10005];
int cnt_[10005]={0};
void add(int op,int ed,int len)
{
cnt++;
b[cnt].len=len;
b[cnt].to=ed;
b[cnt].next=head[op];
head[op]=cnt;
}
bool spfa(int u)
{
vis[u]=1;
for(int i=head[u];i!=-1;i=b[i].next)
{
if(dis[b[i].to]>dis[u]+b[i].len)
{
if(vis[b[i].to]==1)
return false;
dis[b[i].to]=dis[u]+b[i].len;
if(!spfa(b[i].to))
return false;
}
}
vis[u]=0;
return true;
}
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 x;
scanf("%d",&x);
if(x==1)
{
int e,r,s;
scanf("%d%d%d",&e,&r,&s);
add(r,e,-s);
}
if(x==2)
{
int e,r,s;
scanf("%d%d%d",&e,&r,&s);
add(e,r,s);
}
if(x==3)
{
int e,r;
scanf("%d%d",&e,&r);
add(e,r,0);
add(r,e,0);
}
}
memset(dis,0x7f,sizeof(dis));
for(int i=1;i<=n;i++)
add(0,i,0);
dis[0]=0;
if(spfa(0))
printf("Yes");
else
printf("No");
return 0;
}