差分约束系统:
简介: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; }