差分约束系统:
简介:https://blog.csdn.net/weixin_45012616/article/details/100880099
做法: https://www.cnblogs.com/cytus/p/9511604.html

讲解:https://www.bilibili.com/video/BV1ND4y147Pi/?spm_id_from=333.1007.top_right_bar_window_history.content.click
求解差分约束系统,可以转化成图论的单源最短路径问题。观察,会发现它类似最短路中的三角不等式
,即
。因此,以每个变数
为结点,对于约束条件
,连接一条边
,边权为
。再增加一个原点
与所有定点相连,边权均为0。对这个图以s为原点运行Bellman-ford算法(或SPFA算法),最终
即为一组可行解。
经常出现的是i与i-1连接,或者i与i+1连接,应该是小的连向大的
很多题目看上去是数值方面的题,但是可以通过巧妙地建图做转换,所以建图YYDS
题目:
1509:【例 1】Intervals

满足多个不等式的条件下求解某样东西时,可以把这些不等式转化成图论中求最短路的松弛公式然后
把问题转换成求解最短(长)路。这道题就是一个很标准的差分约束系统。
要求满足的不等式包括s[b[i]]-s[a[i]-1]≥c[i],0≤s[i]-s[i-1]≤1,其中s[i]表示到第i个位置为止选择的点的个数,转换一下,就可以得到:
s[b[i]]≥s[a[i]-1]+c[i],
s[i]≥s[i-1]+0,
s[i-1]≥s[i]+(-1),
这就得到了三种边权的边,就可以建图了。

#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<stack>
#include<cstdio>
#include<queue>
#include<map>
#include<vector>
#include<set>
using namespace std;
const int maxn=5e4+10;
const int INF=0x3fffffff;
typedef long long LL;
//差分约束系统标准题
int head[maxn],dis[maxn],vis[maxn];
struct node{
int to,dis,next;
}ed[maxn*2];
int cnt,n;
void add(int x,int y,int z){
ed[++cnt].dis=z;
ed[cnt].to=y;
ed[cnt].next=head[x];
head[x]=cnt;
}
queue<int> q;
void spfa(int st){
for(int i=1;i<=n;i++) dis[i]=-INF;
//因为是求最长路
dis[st]=0;
q.push(st);
vis[st]=1;
while(!q.empty()){
int t=q.front();
q.pop();
vis[t]=0;
for(int i=head[t];i;i=ed[i].next){
int op=ed[i].to;
if(dis[op]<dis[t]+ed[i].dis){ //最长路
dis[op]=dis[t]+ed[i].dis;
if(!vis[op]){
vis[op]=1;
q.push(op);
}
}
}
}
}
int main(){
scanf("%d",&n);
int x,y,z;
int st=INF,ed=-INF;
for(int i=1;i<=n;i++){
scanf("%d %d %d",&x,&y,&z);
add(x-1,y,z); //建边
st=min(st,x-1); //记得减一
ed=max(ed,y);
}
for(int i=st;i<=ed;i++){
add(i-1,i,0);
add(i,i-1,-1);
}
spfa(st);
printf("%d\n",dis[ed]);
return 0;
}
1510:【例 2】出纳员问题

https://blog.csdn.net/aiyunyan0969/article/details/102142702
//讲解: https://blog.csdn.net/weixin_43907802/article/details/100308865
这篇国集论文里面有,这篇论文很不错 数与图的完美结合-------浅析差分约束系统 华中师大一附中 冯威
设num[i] 为来应聘的在第i个小时开始工作的人数
r[i] 为第i个小时至少需要的人数
x[i] 为招到的在第i个小时开始工作的人数
根据题意有:
0 <= x[i] <= num[i]
x[i] + x[i-1] + …+ x[i-7] >= r[i] (题目中的连续工作8小时)
再设 s[i] = x[1] + … + x[i]
则有: s[i] – s[i-1] >= 0
s[i-1] – s[i] >= –num[i]
s[i] – s[i-8] >= r[i], 8 <= i <= 24
s[i] – s[i+16] >= r[i] – s[24], 1<= i <= 7
还需要添加一个隐藏不等式: s[24] – s[0] >= ans(枚举的答案)
通过枚举s[24],来检测是否满足条件(题目是求最小值,即求最长路,以0为源点),每次跑完spfa后,就看算出来的s[24]和枚举的是不是一样就行,因为spfa求出来的是当前约束条件下的
最小值,于是只要从小到大枚举ans和s[24]相等了就说明当前的最小值是ans
#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<stack>
#include<cstdio>
#include<queue>
#include<map>
#include<vector>
#include<set>
using namespace std;
const int maxn=1010;
const int INF=0x3fffffff;
typedef long long LL;
//https://blog.csdn.net/aiyunyan0969/article/details/102142702
//讲解: https://blog.csdn.net/weixin_43907802/article/details/100308865
/*
这篇国集论文里面有,这篇论文很不错 数与图的完美结合-------浅析差分约束系统 华中师大一附中 冯威
设num[i] 为来应聘的在第i个小时开始工作的人数
r[i] 为第i个小时至少需要的人数
x[i] 为招到的在第i个小时开始工作的人数
根据题意有:
0 <= x[i] <= num[i]
x[i] + x[i-1] + …+ x[i-7] >= r[i] (题目中的连续工作8小时)
再设 s[i] = x[1] + … + x[i]
则有: s[i] – s[i-1] >= 0
s[i-1] – s[i] >= –num[i]
s[i] – s[i-8] >= r[i], 8 <= i <= 24
s[i] – s[i+16] >= r[i] – s[24], 1<= i <= 7
还需要添加一个隐藏不等式: s[24] – s[0] >= ans(枚举的答案)
通过枚举s[24],来检测是否满足条件(题目是求最小值,即求最长路,以0为源点),每次跑完spfa后,就看算出来的s[24]和枚举的是不是一样就行,因为spfa求出来的是当前约束条件下的
最小值,于是只要从小到大枚举ans和s[24]相等了就说明当前的最小值是ans
*/
struct node{
int to;
int len;
int next;
}ed[500000];
int head[100000];
int r[10000]; //需要的人数
int t[10000]; //应征的人数
int s[10000]; //0---i雇佣的人数
int cnt,n,dis[10000];
queue<int> q;
void adde(int a,int b,int c){
ed[++cnt].to=b;
ed[cnt].len=c;
ed[cnt].next=head[a];
head[a]=cnt;
}
void ju(int x){
memset(head,-1,sizeof(head));
adde(0,24,x); //这一步
for(int i=1;i<=24;i++){
adde(i-1,i,0); ///连线
adde(i,i-1,-t[i]); //应征的人数
}
for(int i=1;i<=16;i++){
adde(i,i+8,r[i+8]); //需要的人数
}
for(int i=17;i<=24;i++){
adde(i,i-16,r[i-16]-x);
}
}
int vis[110],num[110];
int spfa(int x){
while(!q.empty()) q.pop();
for(int i=1;i<=25;i++) dis[i]=-INF; //求最长路
dis[0]=0;
q.push(0);
memset(num,0,sizeof(num));
memset(vis,0,sizeof(vis));
vis[0]=1;
num[0]=1;
while(!q.empty()){
int op=q.front();
q.pop();
vis[op]=0;
for(int i=head[op];i!=-1;i=ed[i].next){
int tt=ed[i].to;
if(dis[tt]<dis[op]+ed[i].len){
dis[tt]=dis[op]+ed[i].len;
if(!vis[tt]){
vis[tt]=1;
num[tt]++;
if(num[tt]>24) return -1;
q.push(tt);
}
}
}
}
if(dis[24]==x) return 1;
else return 0;
}
int main(){
int T,x;
scanf("%d",&T);
while(T--){
memset(t,0,sizeof(t));
memset(r,0,sizeof(r));
for(int i=1;i<=24;i++){
scanf("%d",&r[i]); //输入需要的人数
}
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&x);
t[x+1]++; //应征的人数
}
int i;
for(i=0;i<=n;i++){
ju(i);
if(spfa(i)>0){
printf("%d\n",i);
break;
}
}
if(i==n+1) {
printf("No Solution\n");
}
}
return 0;
}
1511:【SCOI2011】糖果

这道题算是模板题了,记得是小的连向大的
然后还有一些判断条件
建立一个超级源点,但是这道题对spfa卡时间,所以倒着连(why!!!
因为这道题卡时间,卡SPFA,所以倒着连边(0-->1~n)
建立超级源点是因为可能不是连通图,而且因为每个小朋友都要分到糖果,所以超级源点连接权值为1
而且图里面可能有正环????(这个咋知道的,所以在SPFA里面要判断入队次数(因为是最长路
顺嘴一提:相等是连边add(x,y,0),add(y,x,0)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=3e5+5;
int n,k,h[N],net[N],to[N],cnt,w[N],dis[N],tot[N];
bool vis[N];
queue<int>q;
inline void add(int u,int v,int c) {
to[++cnt]=v;net[cnt]=h[u];h[u]=cnt;w[cnt]=c;
}
int main(){
scanf("%d%d",&n,&k);
int u,v,f;
while (k--) {
scanf("%d%d%d",&f,&u,&v);
if (f==1) add(u,v,0),add(v,u,0);
else
if(f==2) {
if(u==v){puts("-1");return 0;}
add(u,v,1);
}
else if(f==3) add(v,u,0);
else if(f==4) {
if(v==u){puts("-1");return 0;}
add(v,u,1);
}
else if(f==5)add(u,v,0);
}
for(int i=n;i>=1;i--) add(0,i,1);
vis[0]=1,q.push(0);
while(!q.empty()){
int u=q.front();q.pop();vis[u]=0;
if(tot[u]==n-1) { puts("-1");return 0; }
tot[u]++;
for(int i=h[u];i;i=net[i])
if(dis[to[i]]<dis[u]+w[i]){
dis[to[i]]=dis[u]+w[i];
if(!vis[to[i]]) vis[to[i]]=1,q.push(to[i]);
}
}
ll ans=0;
for (int i=1;i<=n;i++) ans+=dis[i];
printf("%lld\n",ans);
return 0;
}
再看一下别的题解,发现大家很多还缩了点,用tarjin,或者是用的并查集合并了起来,懒得看了
1512:排队布局
读题!!!真的读题啊啊啊啊

这道题相比上一个多了一个求出1号和n号之间可以相隔任意长度的判断
//对于约束条件,转化为di-dj<=x;求dn-d1最大值;
//于是对于di-dj<=x,由j向i连x的边,跑最短路。
//如果存在负环,输出-1;(负环:因为是求最短路
根据题目:(which i miss
牛的编号是从小到大,牛可以在同一个位置呆着
然后建边:最多相隔-----b-a<=k 建边为a-->b k
至少相隔----b-a>=k ---> a-b<=-k 建边 b--->a -k
然后是牛可以在同一个位置呆着add(i+1,i,0); 因为(i+1)-(i)>=0 所以 i-(i+1)<=0
#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<stack>
#include<cstdio>
#include<queue>
#include<map>
#include<vector>
#include<set>
using namespace std;
const int maxn=1010;
const int maxm=20050;
const int INF=0x3fffffff;
typedef long long LL;
typedef unsigned long long ull;
//这道题相比上一个多了一个求出1号和n号之间可以相隔任意长度的判断
//对于约束条件,转化为di-dj<=x;求dn-d1最大值;
//于是对于di-dj<=x,由j向i连x的边,跑最短路。
//如果存在负环,输出-1;
//连边:一定要读题目!!!!!!!!!!!!!!!!!!1
int n,ml,md,tot;
int head[maxn],to[maxm],wei[maxm],nex[maxm],num[maxn];
LL dis[maxn];
int vis[maxn];
queue<int> q;
void add(int u,int v,int c){
to[++tot]=v;
wei[tot]=c;
nex[tot]=head[u];
head[u]=tot;
}
int spfa(){
//跑最短路
fill(dis,dis+maxn,INF);
dis[1]=0;
vis[1]=1;
q.push(1);
num[1]++;
while(!q.empty()){
int top=q.front();
q.pop();
vis[top]=0;
for(int i=head[top];i;i=nex[i]){
int tt=to[i];
if(dis[tt]>dis[top]+wei[i]){
dis[tt]=dis[top]+wei[i];
if(!vis[tt]){
vis[tt]=1;
q.push(tt);
num[tt]++;if(num[tt]>n) return -1;
}
}
}
}
if(dis[n]!=INF) return dis[n];
else return -2;
}
int main(){
scanf("%d %d %d",&n,&ml,&md);
int k,a,b;
//aaaaaaa你要读题呀!!编号是站的顺序呀!!!!所以如果顺序不对要交换
for(int i=0;i<ml;i++){
scanf("%d %d %d",&a,&b,&k); //最多相隔k b-a<=k
if(a>b) swap(a,b);
add(a,b,k);
}
for(int i=0;i<md;i++){
scanf("%d %d %d",&a,&b,&k); //最少相隔k b-a>=k --> a-b<=-k
if(a>b) swap(a,b);
add(b,a,-k);
}
//处理这句话:
//所以可能有两头或者更多奶牛站在同一位置上。即使说,如果我们想象奶牛是站在一条数轴上的话,允许有两头或更多奶牛拥有相同的横坐标。
//相邻的要保证顺序
for(int i=1;i<n;i++) add(i+1,i,0);
printf("%d\n",spfa());
return 0;
}
后面这两道题,都有建立超级源点的作用,在这样题目里面,通过建模得到的边,可以通过控制权重建立超级源点,就解决了一些不连通的问题
1595 -- 【差分约束系统】工程规划1252
造一栋大楼是一项艰巨的工程,它是有n个子任务构成的,给它们分别编号1,2,3,....,n(5<=n<=1000).由于对一些任务的起始条件有着严格地限制,所以每个任务的起始时间T1,T2,T3....,Tn并不是很容易确定的(但这些起始时间都是非负整数,因为它们必须在整个工程开始后启动).例如:挖掘完成后,紧接着就要打地基;但是混泥土浇筑完成后,却要等待一段时间再去掉模板.
这种要求就可以用m(5<=m<=5000)个不等式表示,不等式形如ti-tj<=B代表i和j的起始时间必须满足的条件.每个不等式的右边都是一个常数B,这些常数可能不相同,但是它们都在区间(-100,100)内.
你的任务就是写一个程序,当给定像上面那样的不等式后,找出一种可能的起始时间序列T1,T2,T3....,Tn,或者判断问题无解.对于有解的情况,要使最早进行的哪个任务和整个工程的起始时间,也就是说,T1,T2,T3....,Tn中至少有一个0.
Input
Output
#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<stack>
#include<cstdio>
#include<queue>
#include<map>
#include<vector>
#include<set>
using namespace std;
const int maxn=5010;
const int maxm=5010;
const int INF=0x3fffffff;
typedef long long LL;
typedef unsigned long long ull;
int n,m,tot;
int dis[maxn],vis[maxn];
int head[maxm],to[maxm],wei[maxm],nex[maxm];
int num[maxn];
void adde(int x,int y,int w){
to[++tot]=y;
nex[tot]=head[x];
wei[tot]=w;
head[x]=tot;
}
//我的问题就是:一定要先求出哪些是0开始的这种死脑筋---所以更简单的想法就忽略了---求出来,再减去最小的
bool spfa(int x){
queue<int> q;
q.push(x);
dis[x]=0;
vis[x]=1;
while(!q.empty()){
int x=q.front();
q.pop();
vis[x]=0;
++num[x];
if(num[x]>=n) return false;
for(int i=head[x];i;i=nex[i]){
int t=to[i];
if(dis[t]>dis[x]+wei[i]){
dis[t]=dis[x]+wei[i];
if(!vis[t]){
q.push(t);vis[t]=1;
}
}
}
}
return true;
}
int main(){
scanf("%d %d",&n,&m);
int x,y,z;
for(int i=1;i<=m;i++){
scanf("%d %d %d",&x,&y,&z);
adde(y,x,z);
}
memset(dis,0x3f,sizeof(dis));
//建立一个超级源点,避免用连通块
for(int i=1;i<=n;i++) adde(n+1,i,0);
if(spfa(n+1)==false){
printf("NO SOLUTION");return 0;
}
int kk=INF;
for(int i=1;i<=n;i++){
kk=min(kk,dis[i]);
}
for(int i=1;i<=n;i++){
printf("%d\n",dis[i]-kk);
}
return 0;
}
//最后一个点错了????
#include<cstdio>
#include<algorithm>
#include<iostream>
#include<queue>
#include<cstring>
using namespace std;
inline int read()
{
int ans = 0,op = 1;
char ch = getchar();
while(ch < '0' || ch > '9')
{
if(ch == '-') op = -1;
ch = getchar();
}
while(ch >= '0' && ch <= '9')
{
(ans *= 10) += ch - '0';
ch = getchar();
}
return ans * op;
}
const int maxn = 2005;
struct egde
{
int to,next,cost;
}e[maxn * 10];
int fir[maxn],alloc;
inline void adde(int u,int v,int w)
{
e[++alloc].next = fir[u];
fir[u] = alloc;
e[alloc].to = v;
e[alloc].cost = w;
}
int n,m;
int dis[maxn],popst[maxn],minm;
bool instack[maxn];
void spfa(int s)
{
queue<int> q;
memset(dis,0x3f,sizeof(dis));
dis[s] = 0;
q.push(s);
instack[s] = 1;
while(q.size())
{
int u = q.front();
q.pop();
popst[u]++;
if(popst[u] > n - 1) { printf("NO SOLUTION"); return;}
instack[u] = 0;
for(int i = fir[u];i;i = e[i].next)
{
int v = e[i].to,w = e[i].cost;
if(dis[v] > dis[u] + w)
{
dis[v] = dis[u] + w;
if(!instack[v]) q.push(v),instack[v] = 1;
}
}
}
for(int i = 1;i <= n;i++) minm = min(minm,dis[i]);
for(int i = 1;i <= n;i++) printf("%d\n",dis[i] - minm);
}
int main()
{
n = read(),m = read();
for(int i = 1;i <= m;i++)
{
int u = read(),v = read(),w = read();
adde(v,u,w);
}
for(int i = 1;i <= n;i++) adde(n + 1,i,0);
spfa(n + 1);
}
1597 -- 【差分约束系统】01串(NOI99)1887
Description
1.si=0或si=1,1<=i<=N;
2.对于S的任何连续的长度为L0的子串sjsj+1…sj+L0-1(1<=j<=N-L0+1),0的个数大于等于A0且小于等于B0;
3.对于S的任何连续的长度为L1的子串sjsj+1…sj+L1-1(1<=j<=N-L1+1),1的个数大于等于A1且小于等于B1;
例如,N=6,A0=1,B0=2,L0=3,A1=1,B1=1,L1=2,则存在一个满足上述所有条件的01串S=010101。
Input
Output
#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<stack>
#include<cstdio>
#include<queue>
#include<map>
#include<vector>
#include<set>
using namespace std;
const int maxn=30010;
const int INF=0x3fffffff;
typedef long long LL;
typedef unsigned long long ull;
int c[maxn];
int head[maxn];
struct node{
int nex,to,wei;
}ed[maxn];
int tot;
int n,a0,b0,l0,a1,b1,l1;
int vis[maxn],num[maxn]; //还要考虑有没有环?是不是每一个c[]都是一个正常的值
void adde(int x,int y,int w){
ed[++tot].to=y;
ed[tot].nex=head[x];
ed[tot].wei=w;
head[x]=tot;
}
int S,N;
bool spfa(){
queue<int> q;
memset(c,0x3f,sizeof(c));
c[S]=0;
q.push(S); //超级源点
memset(vis,0,sizeof(vis));
while(!q.empty()){
int x=q.front();
q.pop();
vis[x]=0;
++num[x];if(num[x]==n+1) return false;
for(int i=head[x];i;i=ed[i].nex){
int t=ed[i].to;
if(c[t]>c[x]+ed[i].wei){
c[t]=c[x]+ed[i].wei;
if(!vis[t]){
vis[t]=1;
q.push(t);
}
}
}
}
for(int i=1;i<=n;i++) if(c[i]==0x3f3f3f3f) return false;
return true;
}
int main(){
scanf("%d %d %d %d %d %d %d",&n,&a0,&b0,&l0,&a1,&b1,&l1);
N=n+2;S=n+1;//建立一个超级源点
for(int i=l0;i<=n;i++){
adde(i,i-l0,b0-l0);
adde(i-l0,i,-a0+l0);
}
for(int i=l1;i<=n;i++){
adde(i-l1,i,b1);
adde(i,i-l1,-a1);
}
for(int i=0;i<n;i++){
adde(i,i+1,1);
adde(i+1,i,0);
}
adde(S,0,0);
if(spfa()) printf("%d",c[n]-c[0]);
else printf("-1");
return 0;
}
posted on
浙公网安备 33010602011771号