差分约束可以求
(1)求不等式的一组可行解
源点需要满足:从源点出发,一定可以走到所有边
步骤:
1.先将不等式 xi <= xj + ck 转换为一条从xi走到xi,长度为ck的一条边
2.找一个超级源点 使得该源点一定可以遍历到所有边
3.从源点求一遍单源最短路
结果一:存在负环则原不等式组一定无解
结果二:没有负环 则dis[i]就是原不等式的一个可行解
(2)如何求最大值或最小值
结论:如果求的是最小值 则应该求最长路 如果求最大值 则应该求最短路
要是求最值 那么肯定会有一个绝对条件使得某个变量大于等于(小于等于)一个常数
方法中的超级源点0的 x0 为 0
差分约束等价于最短路
1.【SCOI2011】糖果
x==1 说明a==b 则a>=b且b>=a
x==2 说明b>a 则b>=a+1
x==3 说明a>=b
x==4 说明a>b 则a>=b+1
x==5 说明b>=a
因为保证每个小孩都有一个糖果,则xi>=x0+1 x0等于0
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=100010,M=300010;
int n,m;
int h[N],e[M],ne[M],w[M],idx;
ll dis[N];
int q[N],cnt[N];
bool st[N];
void add(int a,int b,int c)
{
e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
bool spfa()
{
int hh=0,tt=1;
memset(dis,-0x3f,sizeof dis);
dis[0]=0;
q[0]=0;
st[0]=true;
while(0!=tt)
{
int t=q[--tt];
//if(hh==N) hh=0;
st[t]=false;
for(int i=h[t];~i;i=ne[i])
{
int j=e[i];
if(dis[j]<dis[t]+w[i])
{
dis[j]=dis[t]+w[i];
cnt[j]=cnt[t]+1;
if(cnt[j]>=n+1) return false;
if(!st[j])
{
q[tt++]=j;
//if(tt==N) tt=0;
st[j]=true;
}
}
}
}
return true;
}
int main()
{
scanf("%d%d",&n,&m);
memset(h,-1,sizeof h);
while(m--)
{
int x,a,b;
scanf("%d%d%d",&x,&a,&b);
if(x==1) add(b,a,0),add(a,b,0);
else if(x==2) add(a,b,1);
else if(x==3) add(b,a,0);
else if(x==4) add(b,a,1);
else add(a,b,0);
}
for(int i=1;i<=n;i++)
add(0,i,1);
if(!spfa()) puts("-1");
else
{
ll res=0;
for(int i=1;i<=n;i++)
res+=dis[i];
printf("%lld\n",res);
}
return 0;
}
2.区间(最长路)
条件1:保证Si比Si-1选的数大于或者等于,则Si>=S(i-1)
条件2:由于当前数Si有;两种情况选或者不选,则Si-S(i-1)<=1,则S(i-1)>=Si-1
条件3:题目输入,则Sb-S(a-1)>=c,则Sb>=S(a-1)+c
#include <bits/stdc++.h>
using namespace std;
const int N=50010,M=150010;
int n;
int h[N],e[M],ne[M],w[M],idx;
int q[N];
bool st[N];
int dis[N];
void add(int a,int b,int c)
{
e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
void spfa()
{
int hh=0,tt=1;
memset(dis,-0x3f,sizeof dis);
dis[0]=0;
q[0]=0;
st[0]=true;
while(hh!=tt)
{
int t=q[hh++];
if(hh==N) hh=0;
st[t]=false;
for(int i=h[t];~i;i=ne[i])
{
int j=e[i];
if(dis[j]<dis[t]+w[i])
{
dis[j]=dis[t]+w[i];
if(!st[j])
{
q[tt++]=j;
if(tt==N) tt=0;
st[j]=true;
}
}
}
}
}
int main()
{
scanf("%d",&n);
memset(h,-1,sizeof h);
for(int i=1;i<=50001;i++)
{
add(i-1,i,0);
add(i,i-1,-1);
}
while(n--)
{
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
a++,b++;
add(a-1,b,c);
}
spfa();
printf("%d\n",dis[50001]);
return 0;
}
3.排队布局(最短路)对这题的理解还是很模糊
条件0:两两头牛距离至少为0 则i-(i-1)>=0 对应(i-1)<=i-0
条件1:距离最多为d 则a-b<=d 对应a<=b+d
条件2:距离最少为d 则a-b>=d 对应b<=a-d
由于没有一个点可以到所有点 所以可以假定一个虚拟源点0号点 然后让所有点都在0号点的左边(数轴左边)则xi<=x0 那么就可以从0号点向所有点连一条边长为0的边 就可以从0号点到所有点
在想的时候有超级源点 但是做的时候不需要真的建立这个源点 因为在spfa的第一步0号点就会把其他所有点都更新进队列里面去 所以可以直接把所有点直接加入队列就不用虚拟源点了
由于这里的条件都是相对关系 所以x1的取值是不固定的 所以可以让1号点固定
求1号奶牛和N号奶牛的距离是不是可以任意大的时候 可以让x1=0然后看XN的距离是不是无限大
固定完以后 就是求1号奶牛到n号奶牛的最短路
完全正确代码
#include<iostream>
#include<cstring>
#include<algorithm>
#include<stack>
using namespace std;
typedef long long ll;
int n,m1,m2;
const int N=1e5+10,M=3e5+10;
int e[M],ne[M],h[N],w[M],idx;
int dist[N];
bool st[N];
int q[N];
int cnt[N];
void add(int a,int b,int c)
{
e[idx]=b,ne[idx]=h[a],w[idx]=c,h[a]=idx++;
}
bool spfa(int x)
{
memset(cnt,0,sizeof cnt);
memset(dist,0x3f,sizeof dist);
int hh=0,tt=0;
for(int i=1;i<=x;i++)
{
dist[i]=0;
q[tt++]=i;
st[i]=true;
}
while(hh!=tt)
{
int t=q[hh++];
if(hh==N) hh=0;
st[t]=false;
for(int i=h[t];~i;i=ne[i])
{
int j=e[i];
if(dist[j]>dist[t]+w[i])
{
dist[j]=dist[t]+w[i];
cnt[j]=cnt[t]+1;
if(cnt[j]>=n) return false;
if(!st[j])
{
q[tt++]=j;
if(tt==N)
tt=0;
}
}
}
}
return true;
}
int main()
{
scanf("%d%d%d",&n,&m1,&m2);
memset(h,-1,sizeof h);
for(int i=1;i<n;i++) add(i+1,i,0);
while(m1--)
{
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
if(b<a) swap(a,b);
add(a,b,c);
}
while(m2--)
{
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
if(b<a) swap(a,b);
add(b,a,-c);
}
if(!spfa(n)) puts("-1");
else
{
spfa(1);
if(dist[n]==0x3f3f3f3f) puts("-2");
else printf("%d\n",dist[n]);
}
return 0;
}
4.雇佣收银员
#include <bits/stdc++.h>
using namespace std;
const int N=30,M=100;
int dist[N];
int q[N],cnt[N];
bool st[N];
int r[N],num[N];
int n;
int h[N],e[M],ne[M],w[M],idx;
void add(int a,int b,int c)
{
e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
void built(int s24)//s24是常数也就是枚举的答案
{
memset(h,-1,sizeof h);
idx=0;
for(int i=1;i<=24;i++)
{
add(i-1,i,0);//si-s(i-1)>=0
add(i,i-1,-num[i]);//si-s(i-1)<=num[i]
}
for(int i=8;i<=24;i++) add(i-8,i,r[i]);//当大于等于8时,si-s(i-8)>=ri
for(int i=1;i<=7;i++) add(i+16,i,-s24+r[i]);//当小于8时,si+s(24)-s(i+16)>=ri
add(0,24,s24),add(24,0,-s24);//要保证24这个点是个常数则s(24)<=s(0)+s24,s(24)>=s(0)+s24
}
bool spfa(int s24)//s24是常数也就是枚举的答案
{
built(s24);//建图
//跑一遍spfa
memset(dist,-0x3f,sizeof dist);
memset(st,0,sizeof st);
memset(cnt,0,sizeof cnt);
int hh=0,tt=1;
q[0]=0;
dist[0]=0;
st[0]=true;
while(hh!=tt)
{
int t=q[hh++];
if(hh==N) hh=0;
st[t]=false;
for(int i=h[t];~i;i=ne[i])
{
int j=e[i];
if(dist[j]<dist[t]+w[i])
{
dist[j]=dist[t]+w[i];
cnt[j]=cnt[t]+1;
if(cnt[j]>=25) return false;
if(!st[j])
{
q[tt++]=j;
if(tt==N) tt=0;
st[j]=true;
}
}
}
}
return true;
}
int main()
{
int T;
cin>>T;
while(T--)
{
memset(num,0,sizeof num);
for(int i=1;i<=24;i++) cin>>r[i];
cin>>n;
for(int i=0;i<n;i++)
{
int x;
cin>>x;
num[x+1]++;//把0空出来当虚拟原点
}
bool f=false;
for(int i=0;i<=1000;i++)//枚举s24可能的情况
if(spfa(i))
{
cout<<i<<endl;
f=true;
break;
}
if(!f) puts("No Solution");
}
return 0;
}