差分约束---将不等式转换为图的算法

概念:已知一个差分约束系统(差分约束系统即一种特殊的n元一次不等式组),形如x_i-x_j<=c_k,要求求出是否存在一组解使得所有约束条件满足。由于在最短路中dis[y]<=dis[x]+w与该不等式形式相似,因此,可以利用图论,从对应的j点连向i点,并且对应构建出的图,采用bellmanford算法解决带负权负环的问题。

  • 判断解的存在性

  1. 存在负环说明无解;

  2. 不存在由s到t的路径说明s与t之间不存在约束条件

差分约束系统的规范化:

  • 若仅存在<=关系,本质是求解最短路,若仅存在>=关系,本质是求解最长路

  • 如果存在a-b=c的情况则差分约束条件转化为a-b>=c且a-b<=c

  • 如果变量都是整数意义上的,则a-b<c请务必转化为a-b<=c-1,保证约束系统的规范性方便建图

  • 若<=和>=关系同时存在,题干若问两点差值最小,相当于求解x_i-x_j的最小值,此时相当于求解最长路,将差分约束条件转化为>=关系(两边同乘-1)即可;同理,否则求解最短路。

  • 尤其注意不等关系的隐含

差分约束的应用

  1. 线性约束

思路:这题唯一的不同之处是要求求出可行的一组解,易知任意赋予某个点一个初始的权值,那么可以给与该点相连的其他点赋上’该点权值加上最短边权‘的总和值,但若有些点无法从该点到达则会很别扭,只能赋给它我们初始化的无穷值。因此,不妨建立一个超级源点连接每一条边,既能严谨的判断负环的存在性,也能更好的给出一组解。

  • 有意思的是,如果将超级源点到其他各点的边权设为0,同时超级源点的初始值设为0,则求出的一组解都一定是小于等于0的,因为只有更短的负权路径去更新超级源点到各点的距离,因此求出解非正。而且跟着边权赋值得出一组解的,最终一定都是临界值。若是<=不等式应该是最大值,反之则是最小值

懒得画图详解了,可看算法学习笔记(11): 差分约束

代码实现:

#include<bits/stdc++.h>
#define maxn 10000
#define INF 0x3f3f3f3f
#define ll long long
using namespace std;
int n,m;
int u[maxn],v[maxn];
ll w[maxn],dis[maxn];
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++)
    scanf("%d%d%lld",&v[i],&u[i],&w[i]);
    for(int i=1;i<=n;i++)
    {
        u[i+m]=0;
        v[i+m]=i;
        w[i+m]=1000;//设边权为1000
    }//建立超级源点
    memset(dis,0x3f,sizeof(dis));
    dis[0]=0;
    for(int k=1;k<=n;k++)//因为有n+1个点
    for(int i=1;i<=m+n;i++)
    dis[v[i]]=min(dis[u[i]]+w[i],dis[v[i]]);
    int flag=0;
    for(int i=1;i<=m+n;i++)
    if(dis[v[i]]>dis[u[i]]+w[i])
    flag=1;
    if(flag)
    printf("NO");
    else{
        for(int i=1;i<=n;i++)
        printf("%lld ",dis[i]);//注意,跟着边权赋值,最终一定都是临界值
    }
    system("pause");
}
  1. 区间约束

  • 种树 - 洛谷

  • 思路:利用差分思想,假设d[i]-d[j-1]就是区间上[j,i]种的树的棵数,那么原问题就被转化成了一系列的>=不等式组,构成了差分约束系统。同时还需关注这道题的隐含条件---1>=d[i]-d[i-1]>=0,原因是这是一个整型氛围,需要进行相应的约束。

  • 同时bellman超时,选用spfa大大优化时间复杂度。

#include<bits/stdc++.h>
#include<queue>
#define maxn 100000
#define INF 0x3f3f3f3f
#define ll long long
using namespace std;
int n,m;
ll tot=0,dis[maxn],head[maxn];
struct Edge{
    int to;
    int next;
    ll w;
}edge[maxn];
void addedge(int u,int v,ll w){
    edge[++tot].to=v;
    edge[tot].next=head[u];
    edge[tot].w=w;
    head[u]=tot;
}
void spfa(void)
{
    ll vis[maxn],s;
    queue<ll>q;
    vis[0]=1;
    q.push(0);
    while(!q.empty())
    {
        s=q.front();
        q.pop();
        vis[s]=0;
        for(ll i=head[s];~i;i=edge[i].next)
        {
            ll to=edge[i].to;
            if(dis[to]>dis[s]+edge[i].w)
            {
                dis[to]=dis[s]+edge[i].w;
                if(!vis[s])vis[to]=1,q.push(to);
            }
        }
    }
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=0;i<=n;i++)
    head[i]=-1;
    for(int i=1;i<=m;i++)
    {
        int u,v;
        ll w;
        scanf("%d%d%lld",&u,&v,&w);
        addedge(u-1,v,-w);
    }//求最长路
    for(int i=1;i<=n;i++)
    {
        addedge(i,i-1,1);
        addedge(i-1,i,0);
    }
    memset(dis,0x3f,sizeof(dis));
    dis[0]=0;
    spfa();
    printf("%lld",-dis[n]);
    system("pause");
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值