差分约束
差分约束
讲解
如果一个不等式组由 \(n\) 个变量和 \(m\) 个约束条件组成,形成 \(m\) 个形如\(x[ j ]-x[ i ]≤k\)(\(i,j∈[1,n]\) 且 \(k\) 为常数)的不等式,则称其为差分约束系统。换句话说,差分约束系统就是求解一组变量的不等式组的算法。
\(d_v-d_u<=w\) 和 spfa 中的松弛操作 \(d_v<=d_u+w\) 非常像
只要没有负环则能得到⼀组合法的{\(d_i\)} ,则原问题有解且 {\(d_i\)} 就是⼀组可行解
做题步骤
1.判断是让求至多(最短路)还是至少(最长路)
2.按照最短/长路松弛操作,对应题目约束条件连边
3.看题目中一些特殊要求,继续完善连边
4.建立超级源点0,向每个点连边权为 0 的边
最后的答案输出:
一般是让输出 dis[n] , dis[1~n], 无解:最短路有负环,最长路有正环(用cnt[ ]判断每个点取出次数是不是≥n )
spfa
k;相当于限制\(x[ i ]\)的最大/小值
连边后求最短路
将\(x[ i ]−x[ j ]≤k\) 变形为 \(x[ i ]≤k+x[ j ]\),即从 \(j\) 到 \(i\) 连一条边权为 \(k\) 的边。加入超级源点后求最短路,得到\(x[i] ≤ k\) 所有 \(x\) 最大解。
连边后求最长路
将\(x[ i ]−x[ j ]≥k\) 变形为 \(x[ j ]≤x[ i ]-k\),即从 \(i\) 到 \(j\) 连一条边权为 \(-k\) 的边。加入超级源点后求最长路,得到\(x[i]≥k\) 所有 \(x\) 最小解。
判负环spfa必然退化为O(nm)
其他未写的题目:
附上一篇有超详细解析和题目的blog
模板
一定注意建超级源点
#include <queue>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N=5005;
inline 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*10+ch-'0';ch=getchar();}
return f*x;
}
int n,m;
int hd[N],to[N<<1],tot,nxt[N<<1],w[N<<1];
inline void add(int x,int y,int z) {
to[++tot]=y;w[tot]=z;nxt[tot]=hd[x];hd[x]=tot;
}
queue<int>q;
int dis[N],cnt[N];
bool vis[N];
bool spfa() {
q.push(0);
dis[0]=0;
cnt[0]=1;
while(q.size()) {
int x=q.front();q.pop();
vis[x]=0;
for(int i=hd[x];i;i=nxt[i]) {
int y=to[i];
if(dis[y]>dis[x]+w[i]) {
dis[y]=dis[x]+w[i];
if(++cnt[y]>n) return 1;
if(!vis[y]) vis[y]=1,q.push(y);
}
}
}
return 0;
}
int main() {
memset(dis,0x3f,sizeof(dis));
n=read();m=read();
for(int i=1;i<=m;i++) {
int u=read(),v=read(),w=read();
add(v,u,w);
}
for(int i=1;i<=n;i++)
add(0,i,0);
if(spfa()) puts("NO");
else {
for(int i=1;i<=n;i++)
printf("%d ",dis[i]);
}
return 0;
}
种树
求最少种树量——最长路
\(dis[y]<dis[x-1]+w[i]\) —— 区间 \([x,y]\)至少种 \(w[i]\) 棵树
for(int i=0;i<=n;i++){
if(i!=0)add(i-1,i,0),dis[i]=-inf; // [i-1,i]最少种0棵
if(i!=n)add(i,i-1,-1);//[i-1,i]最多种1棵
}
#include <queue>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N=50005;
inline 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*10+ch-'0';ch=getchar();}
return f*x;
}
const int inf=0x3f3f3f3f;
int n,m;
int hd[N],to[N<<1],tot,nxt[N<<1],w[N<<1];
inline void add(int x,int y,int z) {
to[++tot]=y;w[tot]=z;nxt[tot]=hd[x];hd[x]=tot;
}
queue<int>q;
int dis[N],cnt[N];
bool vis[N];
bool spfa() {
q.push(0);
dis[0]=0;
cnt[0]=1;
while(q.size()) {
int x=q.front();q.pop();
vis[x]=0;
for(int i=hd[x];i;i=nxt[i]) {
int y=to[i];
if(dis[y]<dis[x]+w[i]) {
dis[y]=dis[x]+w[i];
if(++cnt[y]>n) return 1;
if(!vis[y]) vis[y]=1,q.push(y);
}
}
}
return 0;
}
int main() {
n=read();m=read();
for(int i=1;i<=m;i++) {
int u=read(),v=read(),w=read();
add(u-1,v,w);
}
for(int i=0;i<=n;i++) {
if(i) add(i-1,i,0),dis[i]=-inf;
if(i!=n) add(i,i-1,-1);
}
if(spfa()) puts("NO");
else printf("%d ",dis[n]);
return 0;
}
也可用贪心和线段树优化
POJ 3169 Layout
https://www.luogu.com.cn/problem/P4878
注意限制 i+1 ---> i 连 0 (牛可以在同一个位置)
从0开始跑spfa判断图是不是联通的——洛谷毒瘤hack
#include <queue>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N=2005;
const int M=1000005;
inline int read() {
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
return f*x;
}
int n,m1,m2;
int hd[N],to[M],tot,nxt[M],w[M],cnt[N];
inline void add(int x,int y,int z) {
to[++tot]=y;w[tot]=z;nxt[tot]=hd[x];hd[x]=tot;
}
bool vis[N];
long long dis[N];
bool spfa(int x) {
queue<int>q;
for(int i=0;i<=n;i++) dis[i]=0x3f3f3f3f,vis[i]=0;
q.push(x);dis[x]=0;
while(!q.empty()) {
int x=q.front();q.pop();
vis[x]=0;
for(int i=hd[x];i;i=nxt[i]) {
int y=to[i];
if(dis[y]>dis[x]+w[i]) {
dis[y]=dis[x]+w[i];
if(!vis[y]) {
vis[y]=1;q.push(y);
if(++cnt[y]==n) {
puts("-1");return 1;
}
}
}
}
}
return 0;
}
int main() {
n=read();m1=read();m2=read();
int a,b,c;
for(int i=1;i<=m1;i++) {
a=read();b=read();c=read();
add(a,b,c);
}
for(int i=1;i<=m2;i++) {
a=read();b=read();c=read();
add(b,a,-c);
}
for(int i=1;i<=n;i++)
add(0,i,0);
for(int i=1;i<n;i++)
add(i+1,i,0);
if(spfa(0)) return 0;
spfa(1);
printf("%lld\n",dis[n]==0x3f3f3f3f?-2:dis[n]);
return 0;
}
小 K 的农场
- 农场a比农场b至少多种植了c个单位的作物, \(dis[a]-dis[b]>c\) , \(dis[b]<dis[a]-c\) ; \(a\)到\(b\)连一条 \(-c\)
- 农场a比农场b至多多种植了c个单位的作物,\(dis[a]-dis[b]<c\) , \(dis[a]<dis[b]+c\) ; \(b\)到\(a\)连一条 \(c\)
- 农场\(a\)与农场\(b\)种植的作物数一样多。\(a-->b\)连一条\(0\) \(b-->a\)连一条\(0\)
然后快乐AC
#include <queue>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N=100005;
inline 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*10+ch-'0';ch=getchar();}
return f*x;
}
const int inf=0x3f3f3f3f;
int n,m;
int hd[N],to[N<<1],tot,nxt[N<<1],w[N<<1];
inline void add(int x,int y,int z) {
to[++tot]=y;w[tot]=z;nxt[tot]=hd[x];hd[x]=tot;
}
queue<int>q;
int dis[N],cnt[N];
bool vis[N];
bool spfa() {
memset(dis,0x3f,sizeof(dis));
q.push(0);
dis[0]=0;
cnt[0]=1;
while(q.size()) {
int x=q.front();q.pop();
vis[x]=0;
for(int i=hd[x];i;i=nxt[i]) {
int y=to[i];
if(dis[y]>dis[x]+w[i]) {
dis[y]=dis[x]+w[i];
if(++cnt[y]>n) return 1;
if(!vis[y]) vis[y]=1,q.push(y);
}
}
}
return 0;
}
int main() {
n=read();m=read();
for(int i=1;i<=m;i++) {
int op=read(),u=read(),v=read(),ww;
if(op==1) ww=read(),add(u,v,-ww);
else if(op==2) ww=read(),add(v,u,ww);
else add(u,v,0),add(v,u,0);
}
for(int i=1;i<=n;i++) add(0,i,0);
puts(spfa()?"No":"Yes");
return 0;
}
糖果
这题卡邻接表???vector 可过
好像正解是tarjan —— this
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<vector>
#include<utility>
using namespace std;
#define N 300005
#define inf 0x3f3f3f3f
inline int read(){
int x=0;char ch=getchar();
while(!isdigit(ch)) ch=getchar();
while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
return x;
}
vector < pair<int,int> > g[N];
queue < int > q;
int n,m;
int dis[N],cnt[N];
bool vis[N],f;
long long ans;
int main(){
n=read();m=read();
for(int i=1,op,x,y;i<=m;i++){
op=read();x=read();y=read();
if(op==1) g[x].push_back(make_pair(y,0)),g[y].push_back(make_pair(x,0));
else if(op==2) g[x].push_back(make_pair(y,1));
else if(op==3) g[y].push_back(make_pair(x,0));
else if(op==4) g[y].push_back(make_pair(x,1));
else g[x].push_back(make_pair(y,0));
if(x==y&&(op==2||op==4)){puts("-1");return 0;}
}
for(int i=1;i<=n;i++)
g[0].push_back(make_pair(i,1));
memset(dis,-0x3f,sizeof(dis));
q.push(0);vis[0]=1;dis[0]=0;
while(!q.empty()){
int x=q.front();q.pop();
vis[x]=0;
for(int i=0,siz=g[x].size();i<siz;i++){
int y=g[x][i].first,w=g[x][i].second;
if(dis[y]<dis[x]+w){
dis[y]=dis[x]+w;
if(++cnt[y]==n){ puts("-1"); return 0;}
if(!vis[y]){
vis[y]=1;q.push(y);
}
}
}
}
for(int i=1;i<=n;i++)
ans+=dis[i];
printf("%lld\n",ans);
return 0;
}