抱歉图论两天没更了,今天更一个差分约束,这也是比较有用的一个知识点吧。
差分约束系统是什么呢?给定一些约束条件,然后求出有/没有符合这个约束条件的对其中每个元素的取值,当然,也能算具体的值是多少。
对了,看这篇博客之前先学会SPFA算法。一道例题:luoguP1993
题目大意:有n个变量和一些关系条件,每一种关系条件都形如以下:a>=b+x,a<=b+x或a=b,x是个常数,问这些关系条件是否有矛盾。
差分约束解析:差分约束中的约束条件就是和这道题的条件的形式一样,比如a>=b+x,看到这个,有没有想起SPFA中更新dis[y]是的式子
dis[y]<=dis[x]+val[i]?如果有a<=b+x是不是就能建一个从b到a的路径,权值为x,然后跑最短路就行了。如果建边的那块没有看懂也没事
可以看下面那张图片,清楚的解释了差分约束系统的建边。

看完这个图之后应该就明白了差分约束的建边原理了,但是还有一类边a>=b+x怎么建呢?很好办,做一个数学转化,变成b<=a+(-x),
是不是现在会建了?就建一条从b到a长度为-x的边就行了。但是这道题中的判断是否成立却有一点难度,怎么叫不成立呢?是不是就是
先说a>=b+x再说a<=b+y且y<x,再看最短路中,就是先建了a->b,-x再建b->a,y,又因为y<x,所以y-x<0所以这种情况就会出现负环
还有几种不成立都会导致负环的产生,所以可以用spfa判一波负环,如果有负环,输出NO,否则输出YES。代码如下:
#include<bits/stdc++.h> #define xms cte #define int long long using namespace std; const int NR=10005; const int INF=1e18+10; int n,m,s; int read() { int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();} return x*f; } int to[NR*3],nxt[NR*3],val[NR*3]; int head[NR]; int tot=1; void add(int x,int y,int z) { to[tot]=y; val[tot]=z; nxt[tot]=head[x]; head[x]=tot++; } bool flag=0; int dis[NR],cnt[NR]; int cnt2[NR]; bool vis[NR]; void SPFA() { for(int i=0;i<=n;i++) dis[i]=-1e18; queue<int> q; q.push(s); vis[s]=1,dis[s]=0; while(!q.empty()) { int x=q.front(); q.pop(); // cout<<x<<endl; cnt2[x]++; vis[x]=0; if(cnt2[x]>=n) { flag=1; return; } for(int i=head[x];i;i=nxt[i]) { int y=to[i]; if(dis[y]<dis[x]+val[i]) { dis[y]=dis[x]+val[i]; if(!vis[y]) { q.push(y); vis[y]=1; } } } } } signed main() { // freopen("farm.in","r",stdin); // freopen("farm.out","w",stdout); n=read(),m=read(); for(int i=1;i<=m;i++) { int op; op=read(); if(op==1) { int a=read(),b=read(),c=read(); add(a,b,c); } if(op==2) { int a=read(),b=read(),c=read(); add(b,a,-c); } if(op==3) { int a=read(),b=read(); add(a,b,0); add(b,a,0); } } for(int i=1;i<=n;i++) add(0,i,0); SPFA(); if(!flag) puts("Yes"); else puts("No"); return 0; }
注意在洛谷上提交时必须开O2优化,否则洛谷卡bfs版的spfa
然后,基础的差分约束讲完了,现在来讲一些进阶的。
如果发现这个约束条件没有那么容易怎么办呢?下面我来推荐一道题:POJ1275
题目大意:有一家24小时超市,每个小时最少需要a[i]个员工,有n(n<=1000)个应聘者,每个人都有一个参数x,表示从第x之后8个小时,
这个人能工作,问招聘多少人能满足要求。如果不能满足,则输出No solution。
题目解析:这道题如果不说是差分约束是不是很那想到啊?这道题怎么差分约束呢?首先要确定节点是什么,然后再想别的。我们用s数组
表示前缀和,s[i]表示所有i天之前工作者的数量,易得s[i]-s[i-8]>=a[i],但当i<7时,就会涉及三元不等式,为了解决这个问题,我们可以
枚s[23],并将i<7时变成二元不等式,然后做spfa就行了。建议大家先不看代码,自己写一写这道题。
但我还是将代码放在这里吧
#include<bits/stdc++.h>
using namespace std;
const int NR=105;
const int MR=2e3+10;
int n;
int a[NR],num[NR];
int to[MR],nxt[MR],val[MR];
int head[NR];
int tot=1;
void add(int x,int y,int z)
{
to[tot]=y;
val[tot]=z;
nxt[tot]=head[x];
head[x]=tot++;
}
int read()
{
int x=0,f=1;char ch=getchar();
while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
while(ch<='9'&&ch>='0'){x=(x<<3)+(x<<1)+(ch^48);ch=getchar();}
return x*f;
}
bool vis[NR];
int dis[NR];
void Init()
{
tot=1;
memset(head,0,sizeof(head));
memset(vis,0,sizeof(vis));
memset(dis,-0x3f,sizeof(dis));
}
void build(int x)
{
for(int i=0;i<=23;i++)
{
add(i,i+1,0);
add(i+1,i,-num[i]);
}
for(int i=7;i<=23;i++) add(i-7,i+1,a[i]);
add(0,24,x),add(24,0,-x);
for(int i=0;i<=6;i++) add(i+17,i+1,a[i]-x);
}
bool check(int x)
{
Init();
build(x);
queue<int> q;
q.push(0),dis[0]=0,vis[0]=1;
while(!q.empty())
{
int now=q.front();q.pop();vis[now]=0;
if(now==24&&dis[now]>x) return 0;
for(int i=head[now];i;i=nxt[i])
{
int y=to[i];
if(dis[y]<dis[now]+val[i])
{
dis[y]=dis[now]+val[i];
if(!vis[y])
{
q.push(y);
vis[y]=1;
}
}
}
}
return dis[24]==x;
}
void solve()
{
int ans=-1;
for(int i=0;i<=n;i++)
{
if(check(i))
{
ans=i;
break;
}
}
if(ans<0) puts("No Solution");
else printf("%d
",ans);
}
int main()
{
// freopen("1.in","r",stdin);
// freopen("1.out","w",stdout);
int T=read();
while(T--)
{
memset(num,0,sizeof(num));
for(int i=0;i<=23;i++)
{
a[i]=read();
}
n=read();
for(int i=1;i<=n;i++)
{
int x=read();
num[x]++;
}
solve();
}
return 0;
}