差分约束小记

差分约束

形如 \(n\)\(a_i - b_i \leq c_k\) 其中 \(c_k\) 是一组常数

差分约束里的细节:

  • 超级源点基本都要建,注意 \(0\) 是否被用,是否能当源点。
  • 一些有实际意义的(如距离,个数)都隐含着 \(a \geq 0\) 的条件,注意隐含条件

例题难度基本递增

例题:

I P1260 工程规划

裸题: \(t_i - t_j \leq b\) 变为 \(t_i \leq t_j + b\) 类似于最短路三角形不等式 \(d_y \leq d_x + z\),把 \(j\)\(i\) 连一条长度为 \(b\) 的边,再建一个超级源点 \(0\) 向任意 \(i\) 都连一条长度为 \(0\) 的边,有负环则无解,求出解后因为需要至少有一个为0,所以我们找出 \(d_i\) 中的最小值 \(mi\),把所有 \(d_i\) 都减去 \(mi\) 得到一组符合题意的解。

点击查看代码

#include <bits/stdc++.h>
using namespace std;
const int N = 2e3+10,M = 2e5+10;
//差分约束 
int n,m;
struct made{
    int ver,nx,ed;
}e[M<<1];
int hd[N],tot;
void add(int x,int y,int z){
    tot++;
    e[tot].nx = hd[x],e[tot].ver = y,e[tot].ed = z,hd[x] = tot;
}
int d[N],in[N];
bool v[N];
queue<int>q;
bool spfa(){
    memset(d,0x3f,sizeof(d));
    q.push(0),d[0] = 0,v[0] = 1;
    while(!q.empty()){
        int x = q.front();q.pop();
        v[x] = 0;
        for(int i = hd[x];i;i = e[i].nx){
            int y = e[i].ver,z = e[i].ed;
            if(d[x] + z < d[y]){
                d[y] = d[x] + z;
                in[y]++;
                if(in[y] >= n)return 1;//有负环
                if(!v[y])v[y] = 1,q.push(y);
            }
        }
    }
    return 0;
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i = 1;i <= n;i++)add(0,i,0);//源点
    for(int i = 1;i <= m;i++){
        int x,y,z;
        scanf("%d%d%d",&x,&y,&z);
        add(y,x,z);
    }
    if(spfa()){
        printf("NO SOLUTION");
        return 0;
    }
    int s = INT_MAX;
    for(int i = 1;i <= n;i++)s = min(s,d[i]);//
    for(int i = 1;i <= n;i++)d[i] -= s;
    for(int i = 1;i <= n;i++)printf("%d\n",d[i]);
    
    return 0;
    
}

II 362. 区间/SP116.Intervals

我们设 \(S_i\) 为前 \(i\) 个区间内的整数个数,根据前缀和我们可以得到 \(S_{b_i} - S_{a_i-1} \geq c_i\) 转化为 \(S_{b_i} \geq c_i + S_{a_i-1}\),即 \(a_i-1\)\(b_i\) 连一条长度为 \(c_i\) 的边。然后这道题就做完了
观察观察,本题中我们设的 \(S_i\) 是整数个数,是有意义的,我们必须要加一些条件使它有意义。

  • \(S_i - S_{i-1} \geq 0 \Rightarrow S_i \geq S_{i-1}\),即 \(i-1\)\(i\) 连一条长度为 \(0\) 的边。
  • \(S_i - S_{i-1} \leq 1 \Rightarrow S_{i-1} \geq S_i - 1\) ,即 \(i\)\(i-1\) 连一条长度为 \(-1\) 的边。

最后注意到我们把所有式子转化为了大于号,所以我们要 \(spfa\)最长路(图中有正数不能跑 \(dijstra\)),因为保证有解,不需要判正环,因为有 \(0\) 所以我把全部编号加 \(1\),最后答案就为 \(d[50001]\)

点击查看代码
#include <bits/stdc++.h> 
using namespace std;
//差分约束
const int N = 5e4+10,M = 2e5+10;
int n;
struct made{
    int ver,nx,ed;
}e[M];
int hd[N],tot;
void add(int x,int y,int z){
    tot++;
    e[tot].nx = hd[x],e[tot].ver = y,e[tot].ed = z,hd[x] = tot;
}
int d[N];
bool v[N];
void spfa(){
    memset(d,-0x3f,sizeof(d));
    queue<int>q;
    q.push(0);d[0] = 0,v[0] = 1;
    while(!q.empty()){
        int x = q.front();q.pop();
        v[x] = 0;
        for(int i = hd[x];i;i = e[i].nx){
            int y = e[i].ver,z = e[i].ed;
            if(d[y] < d[x] + z){
                d[y] = d[x] + z;
                if(!v[y])q.push(y),v[y] = 1;
            }
        }    
    }
}
int main(){
    scanf("%d",&n);
    for(int i = 1;i <= n;i++){
        int x,y,z;
        scanf("%d%d%d",&x,&y,&z);
        x++,y++;//从0开始全加1
        add(x-1,y,z);
    }
    for(int i = 1;i <= 50001;i++){
        add(i,i-1,-1);
        add(i-1,i,0);
    }
    spfa();
    printf("%d\n",d[50001]);//答案为50001
    
    return 0;
    
}

III P2294 [HNOI2005] 狡猾的商人

本题较为简单,主要是细节
我们设 \(S_i\) 为前 \(i\) 月账本的和,则题中式子为 \(S_t - S_{s-1} = v \Rightarrow S_t - S_{s-1} \leq v\) 并且 \(S_t - S_{s-1} \geq v\)
\(s-1\)\(t\) 连一条长度为 \(v\) 的边,\(t\)\(s-1\) 连一条长度为 \(-v\) 的边,判断负环即可。
细节:因为我们 \(S\) 为前缀和,\(0\) 已经被占用,所以建超级源点不能以 \(0\) 为源点

点击查看代码
#include <bits/stdc++.h> 
using namespace std;
const int N = 110,M = 2e3+10;
//差分约束 
int t,n,m;
struct made{
    int ver,nx,ed;
}e[M<<1];
int hd[N],tot;
void add(int x,int y,int z) {
    tot++;
    e[tot].nx = hd[x],e[tot].ver = y,e[tot].ed = z,hd[x] = tot;
}
int d[N],in[N];
bool v[N];
bool spfa(){
    memset(in,0,sizeof(in));//
    memset(v,0,sizeof(v));
    memset(d,0x3f,sizeof(d));
    queue<int>q;
    q.push(n+1),d[n+1] = 0,v[n+1] = 1;
    while(!q.empty()){
        int x = q.front();q.pop();
        v[x] = 0;
        for(int i = hd[x];i;i = e[i].nx){
            int y = e[i].ver,z = e[i].ed;
            if(d[y] > d[x] + z){
                d[y] = d[x] + z;
                in[y]++;
                if(in[y] > n)return 1;
                if(!v[y])v[y] = 1,q.push(y);
            }
        }
    }
    return 0;
}
int main(){
    freopen("bzoj_1202.in","r",stdin);
    freopen("bzoj_1202.out","w",stdout);
    scanf("%d",&t) ;
    while(t--){
        tot = 0;
        memset(hd,0,sizeof(hd));
        scanf("%d%d",&n,&m);
        for(int i = 1;i <= m;i++){
            int s,t,v;
            scanf("%d%d%d",&s,&t,&v);
            add(s-1,t,v);
            add(t,s-1,-v);
        }
        for(int i = 1;i <= n;i++)add(n+1,i,0);//源点不能为0
        if(spfa())printf("false\n");
        else printf("true\n");
    }
    
    
    return 0;
    
}

IV P3275 [SCOI2011] 糖果

看到这种 \(a_i \leq b_i\) 的式子直接想差分约束:
首先我们要求的是最小糖果数,要把式子变为 \(\geq\) 的样子跑最长路。

  • $X = 1,S_a = S_b \Rightarrow S_a - S_b \geq 0 $ 并且 $ S_b - S_a \geq 0$,即 \(a\)\(b\) 连一条长度为 \(0\) 的边,\(b\) 也向 \(a\) 连一条长度为 \(0\) 的边。
  • \(X = 2,S_a < S_b \Rightarrow S_b \geq S_a + 1\),即 \(a\)\(b\) 连一条长度为 \(1\) 的边。
  • \(X = 3,S_a \geq S_b\),即 \(b\)\(a\) 连一条长度为 \(0\) 的边。
  • \(X = 4,S_a > S_b \Rightarrow S_a \geq S_b + 1\),即 \(b\)\(a\) 连一条长度为 \(1\) 的边。
  • \(X = 5,S_a \leq S_b \Rightarrow S_b \geq S_a\),即 \(a\)\(b\) 连一条长度为 \(0\) 的边。
  • 因为每个小朋友都至少有一个糖果,满足不等式 \(S_i \geq S_0 + 1\),即 \(0\)\(i\) 连一条长度为 \(1\) 的边。

让后直接跑SPFA
我们观察数据 \(N\) 最大为 \(100000\) 直接跑 \(SPFA\)正环很有可能会炸SPFA已经死了
我们建的图中的边权只有 \(1\)\(0\),我们考虑 \(tarjan\) 找环,当一个强连通分量中有一个边权为 \(1\) 的边,那么一定无解,否则我们可以缩点后,从 \(0\) 的强连通分量中开始拓扑排序求最长路,这样每个强联通分量中的糖果数都相等,我们累加 \(size[i] * d[i]\) 即可。

点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define ll long long
//差分约束 + 强连通分量 
const int N = 2e5+10,M = 4e5+10;
int n,m;
ll ans;
struct made{
    int ver,nx,ed;  
}e[M],E[M];
int hd[N],hde[N],tot,cnt,num,top,te;
int dfn[N],low[N],st[N],c[N],size[N],in[N];
bool v[N];
void add(int x,int y,int z){
    tot++;
    e[tot].nx = hd[x],e[tot].ver = y,e[tot].ed = z,hd[x] = tot;
}
void add_v(int x,int y,int z){
    te++;
    E[te].nx = hde[x],E[te].ver = y,E[te].ed = z,hde[x] = te;
}
void tarjan(int x){
    low[x] = dfn[x] = ++cnt;
    v[x] = 1,st[++top] = x;
    for(int i = hd[x];i;i = e[i].nx){
        int y = e[i].ver;
        if(!dfn[y])tarjan(y),low[x] = min(low[x],low[y]);
        else if(v[y])low[x] = min(low[x],dfn[y]);
    }
    if(dfn[x] == low[x]){
        num++;int y = 0;
        do{
            y = st[top--];
            v[y] = 0,c[y] = num;
            size[num]++;
        }while(x != y);
    }
}//
int d[N];
void topsort(){
    queue<int>q;
    q.push(c[0]);d[0] = 0;
    while(!q.empty()){
        int x = q.front();q.pop();
        ans += 1ll * size[x] * d[x];//累加答案
        for(int i = hde[x];i;i = E[i].nx){
            int y = E[i].ver,z = E[i].ed;
            d[y] = max(d[y],d[x] + z);//最长路
            if(--in[y] == 0)q.push(y);
        }
    }
}//拓扑排序
int main(){
    freopen("tangguo.in","r",stdin);
    freopen("tangguo.out","w",stdout);
    scanf("%d%d",&n,&m);
    for(int i = 1;i <= m;i++){
        int op,x,y;
        scanf("%d%d%d",&op,&x,&y);
        switch(op){
            case 1:add(x,y,0),add(y,x,0);break;
            case 2:add(x,y,1);break;
            case 3:add(y,x,0);break;
            case 4:add(y,x,1);break;
            case 5:add(x,y,0);break;
        }
    }
    for(int i = 1;i <= n;i++)add(0,i,1);//源点
    for(int i = 0;i <= n;i++)
        if(!dfn[i])tarjan(i);
	
    for(int x = 0;x <= n;x++)
        for(int i = hd[x];i;i = e[i].nx){
            int y = e[i].ver,z = e[i].ed;
            if(c[x] == c[y] && z){//一个连通分量中有边权为1的边无解
                printf("-1\n");
                return 0;
            }
            else if(c[x] != c[y])add_v(c[x],c[y],z),in[c[y]]++;
        }
    topsort();
    printf("%lld\n",ans);
    
    return 0;
    
}

V P4926 [1007] 倍杀测量者

写了这道题要女装
首先我们可以找出式子如果 \(a \geq b \times (k - T)\)\(a \times (k + T) > b\) 时任何人都不女装,我们要找到至少有一个人女装\(T\),即该式子有无解的情况,那么我们考虑差分约束,但是这个乘法很烦,我们试着把它变一下,\(log\) 是个好东西,可以化乘为加
变为 \(ln(a) \geq ln(b) + ln(k - T)\)\(ln(a) + ln(k + T) > ln(b)\),这就是模型了,我们只需要二分 \(T\),无解判定即可。
我们知道一些固定的值 \(a_i = k\) ,从源点连边即可。

注:本题精度要求不是很大,不需要太精确,所以 \(>\) 可以大致为 \(\geq\)

点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define ll long long
//乘法差分约束 
const double eps = 1e-6,inf = 1e8;
const int N = 1e3+10,M = 1e5+10;
int n,m,t;
struct made{
    int nx,ver,op;
    double ed;
}e[N<<1];
int hd[N],tot;
void add(int x,int y,double z,int op){
    tot++;
    e[tot].nx = hd[x],e[tot].ver = y,e[tot].ed = z,e[tot].op = op,hd[x] = tot;
}
double d[N];
int in[N];
bool v[N];
bool spfa(double mid){
    memset(v,0,sizeof(v));
    memset(in,0,sizeof(in));
    for(int i = 1;i <= n;i++)d[i] = inf;
    queue<int>q;
    q.push(0),d[0] = 0,v[0] = 1;
    while(!q.empty()){
        int x = q.front();q.pop();
        v[x] = 0;
        for(int i = hd[x];i;i = e[i].nx){
            int y = e[i].ver;
            double z = e[i].ed;
            if(e[i].op == 1)z = -log(z-mid);//第1种边 
            if(e[i].op == 2)z = log(z+mid);//第2种边
            if(d[y] > d[x] + z){
                d[y] = d[x] + z;
                if(!v[y]){
                    v[y] = 1,in[y]++;
                    if(in[y] > n+1)return 1;
                    q.push(y);
                }
            }
        }
    }
    return 0;
}
int main(){
    scanf("%d%d%d",&n,&m,&t);
    double l = 0,r = INT_MAX;
    for(int i = 1;i <= m;i++){
        int op,a,b;double k;
        scanf("%d%d%d%lf",&op,&a,&b,&k);
        add(a,b,k,op);
        if(op == 1)r = min(r,k);//二分右界 
    }
    for(int i = 1;i <= t;i++){
        int x;double z;
        scanf("%d%lf",&x,&z);
        add(0,x,log(z),0),add(x,0,-log(z),0);
    }
    if(!spfa(0))printf("-1\n");//全部人都不需要女装,即不存在T
    else{
        while(l + eps < r){
            double mid = (l + r) / 2;
            if(spfa(mid))l = mid;
            else r = mid;
        }
        printf("%lf\n",r);
    }
    
    return 0;
}

VI P5590 赛车游戏

第一眼看咋看都不像差分约束
因为我们要找的是 \(1 \sim n\)路径长度都相等的一种情况,我们先设 \(d_i\)\(1 \sim i\) 的路径,那么任意边 \((x,y,z)\) 都满足 \(d_x+ z = d_y\)。反证:

  • 我们假设 \(d_x + z \neq d_y\),因为 \(1 \sim n\) 的路径是一定的,那么我们设 \(y \sim n\) 的路径长度为 \(d_{y \sim n}\),则一定有 \(d_x + z + d_{y \sim n} = d_n,d_y + d_{y \sim n} = d_n\) 得到 \(d_x + z = d_y\),与假设相悖,假设不成立。

得证
那么我们又知道每个边的边长在 \([1,9]\) 之内,我们就可以列出 \(1 \leq d_y - d_x \leq 9\),哦~,差分约束,建边即可。
但是其实那些不在 \(1 \sim n\) 的路径上的边没有影响,所以不能建这些边,不然我们可能找到错误的约束条件,这些边输出时任意输出 \([1,9]\) 即可。

点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define ll long long
//差分约束
const int N = 1e3+10,M = N<<3;
int n,m;
struct made{
    int ver,nx,ed;
}e[M],e1[M];
struct node{
    int x,y;
}li[M];
int hd[N],hd1[N],tot;
void add(int x,int y,int z){
    tot++;
    e[tot].nx = hd[x],e[tot].ver = y,e[tot].ed = z,hd[x] = tot;
}
void add1(int x,int y){
    tot++;
    e1[tot].nx = hd1[x],e1[tot].ver = y,hd1[x] = tot;
}
int d[N],in[N];
bool v[M],vi[N];
bool spfa(){
    memset(v,0,sizeof(v));
    memset(d,0x3f,sizeof(d));
    queue<int>q;
    q.push(1),d[1] = 0,v[1] = 1;
    while(!q.empty()){
        int x = q.front();q.pop();
        v[x] = 0;
        for(int i = hd[x];i;i = e[i].nx){
            int y = e[i].ver,z = e[i].ed;
            if(d[y] > d[x] + z){
                d[y] = d[x] + z;
                if(!v[y]){
                    v[y] = 1,in[y]++;
                    if(in[y] > n)return 1;
                    q.push(y);
                }
            }
        }
    }
    return 0;
}
bool dfs(int x){//1~n的路径
    if(x == n || vi[x])return 1;
    for(int i = hd1[x];i;i = e1[i].nx){
        int y = e1[i].ver;
        if(v[i])continue;//记录边
        v[i] = 1;
        if(dfs(y)){//可以到n
            vi[x] = 1;
            add(x,y,9),add(y,x,-1);
        }
    }
    return vi[x];
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i = 1;i <= m;i++){
        int x,y;
        scanf("%d%d",&x,&y);
        add1(x,y);
        li[i].x = x,li[i].y = y;
    }
    tot = 0;
    if(!dfs(1))printf("-1\n");//到不了n
    else if(spfa())printf("-1\n");
    else{
        printf("%d %d\n",n,m);
        for(int i = 1;i <= m;i++){
            int dis = d[li[i].y] - d[li[i].x];
            dis = (dis < 10 && dis > 0)?dis:1;//其他边任意赋值即可
            printf("%d %d %d\n",li[i].x,li[i].y,dis);
        }
    }
    
    return 0;
}
posted @ 2024-01-12 21:05  oXUo  阅读(34)  评论(0编辑  收藏  举报
网站统计