差分约束,就是用spfa解决一下不等式问题
但是,这些不等式的不等号必须统一
当一个不等式形如:sx-sy<=z
那么我们建一条由y到x权值为z的边
有一系列不等式组,按此规则建边(如果有>=就取负在做)
然后,得到了一张有向图,对他跑最短路
跑出来的值,一定满足约束条件
这就是差分约束的原理
至于实现,一般按以下规则:
建立一个类似前缀和数组,得出不等式关系
然后,按题目要求化为同一类不等式,跑最路
最小值 --> >= --> 最长路
最大值 --> <= --> 最短路
以P1250种树来分析
题目:https://www.luogu.com.cn/problem/P1250
我们设si为到第i位共有多少树
得s[e]-s[b-1]>=t
0<=s[i]-s[i-1]<=1
变成>=的不等式:
s[e]-s[b-1]>=t
s[i]-s[i-1]>=0
s[i-1]-s[i]>=-1
建边,跑最长路
#include <bits/stdc++.h> using namespace std; const int N = 3e4+12; int n,m,s[N]; struct edge { int next,to,w; }p[4*N]; int head[N],num; void ad(int x,int y,int z) { p[++num]=edge{head[x],y,z}; head[x]=num; } int dis[N]; bool vis[N]; void spfa() { queue<int> q; memset(dis,143,sizeof(dis)); q.push(0); dis[0]=0; while(!q.empty()) { int u=q.front();q.pop();vis[u]=0; for(int i=head[u];i;i=p[i].next) { int v=p[i].to; if(dis[v]<dis[u]+p[i].w) { dis[v]=dis[u]+p[i].w; if(!vis[v]) vis[v]=1,q.push(v); } // printf("%d %d %d ",u,v,dis[v]); } } } int main() { cin>>n>>m; for(int i=1;i<=m;i++) { int x,y,z; scanf("%d%d%d",&x,&y,&z); x--; ad(x,y,z); // ad(y,x,y-x); } for(int i=1;i<=n;i++) ad(i,i-1,-1),ad(i-1,i,0); spfa(); printf("%d ",max(dis[n],0)); return 0; }
再看一道题:
lgP1993: https://www.luogu.com.cn/problem/P1993
观察这道题,发现他要维护一堆约束条件:
a-b>=c
b-a<=c
a=b
有那么多不等式,而且基本上都是在维护这些不等式,我们想到了差分约束
等等!!!你可能会问:不还有一个等式吗?
所以我们要把等式转成不等式:
a=b ---> a>=b&&b<=a
这样我们就可以差分约束了
然后我们就发现:图是不连通的
所以我们对所有连通块进行搜索
只要有一个连通块有负环就不行
#include <bits/stdc++.h> using namespace std; const int N = 1e4+4; int n,m; struct edge { int next,to,w; }p[4*N]; int head[N],num; void ad(int x,int y,int z) { p[++num]=edge{head[x],y,z}; head[x]=num; } int dis[N],cnt[N]; bool vis[N],vs[N]; bool spfa(int x) { queue<int> q; q.push(x); dis[x]=0; vs[x]=1; while(!q.empty()) { int u=q.front();q.pop();vis[u]=0; if(cnt[u]>=n) return 0; for(int i=head[u];i;i=p[i].next) { int v=p[i].to; vs[v]=1; if(dis[v]>dis[u]+p[i].w) { dis[v]=dis[u]+p[i].w; if(!vis[v]) { cnt[v]++; if(cnt[v]>=n) return 0; vis[v]=1; q.push(v); } } } } return 1; } bool check() { memset(dis,127,sizeof(dis)); memset(vis,0,sizeof(vis)); memset(cnt,0,sizeof(cnt)); for(int i=1;i<=n;i++) if(!vs[i]) if(!spfa(i)) return 0; return 1; } int main() { cin>>n>>m; for(int i=1;i<=m;i++) { int opt,a,b,c; scanf("%d%d%d",&opt,&a,&b); if(opt==1) { scanf("%d",&c); ad(a,b,-c); } if(opt==2) { scanf("%d",&c); ad(b,a,c); } if(opt==3) { ad(b,a,0); ad(a,b,0); } } if(check()) puts("Yes"); else puts("No"); return 0; } /* 钦定一种不等式,在每个连通块中差分约束 a=b ---> a>=b&a<=b 钦定了<=跑最短路,看有没有负环 优化:无用memset,连通块染色,在一个连通块内判断,用连通块的大小来判负环 */
但是一交:70TLE
说明我们还是too young
要进一步优化:
我们发现在一个联通块内有负环,只要入队次数大于连通块大小就可以了
所以我们可以O(n)预处理出所有连通块的大小
dfs处理连通块,bfs-spfa,最坏2s,卡过
#include <bits/stdc++.h> using namespace std; const int N = 1e4+4; int n,m; struct edge { int next,to,w; }p[4*N]; int head[N],num; void ad(int x,int y,int z) { p[++num]=edge{head[x],y,z}; head[x]=num; } int dis[N],cnt[N],col[N],cl,sz[N],g[N]; bool vis[N],vs[N]; bool spfa(int x) { queue<int> q; q.push(g[x]); dis[g[x]]=0; while(!q.empty()) { int u=q.front();q.pop();vis[u]=0; if(cnt[u]>sz[col[u]]) return 0; for(int i=head[u];i;i=p[i].next) { int v=p[i].to; if(dis[v]>dis[u]+p[i].w) { dis[v]=dis[u]+p[i].w; if(!vis[v]) { cnt[v]++; if(cnt[v]>sz[col[u]]) return 0; vis[v]=1; q.push(v); } } } } return 1; } bool check() { memset(dis,127,sizeof(dis)); memset(vis,0,sizeof(vis)); memset(cnt,0,sizeof(cnt)); for(int i=1;i<=cl;i++) if(!spfa(i)) return 0; return 1; } void dfs(int u) { col[u]=cl; sz[cl]++; for(int i=head[u];i;i=p[i].next) { int v=p[i].to; if(!col[v]) dfs(v); } } int main() { cin>>n>>m; for(int i=1;i<=m;i++) { int opt,a,b,c; scanf("%d%d%d",&opt,&a,&b); if(opt==1) { scanf("%d",&c); ad(a,b,-c); } if(opt==2) { scanf("%d",&c); ad(b,a,c); } if(opt==3) { ad(b,a,0); ad(a,b,0); } } for(int i=1;i<=n;i++) if(!col[i]) { cl++; g[cl]=i; dfs(i); } if(check()) puts("Yes"); else puts("No"); return 0; } /* 钦定一种不等式,在每个连通块中差分约束 a=b ---> a>=b&a<=b 钦定了<=跑最短路,看有没有负环 优化:无用memset,连通块染色,在一个连通块内判断,用连通块的大小来判负环 */