小明的银行卡

虽然小明并没有多少钱,但是小明办了很多张银行卡,共有 n 张,以至于他自己都忘记了每张银行卡里有多少
钱了。  他只记得一些含糊的信息,这些信息主要以下列三种形式描述:
 1. 银行卡a 比银行卡b 至少多c 元。  2. 银行卡a 比银行卡b 至多多c 元。  3. 银行卡a 和银行卡c 里的存款一样多。  但是由于小明的记忆有些差,他想知道是否存在一种情况,使得银行卡的存款情况和他记忆中的所有信息吻合。  输入格式: 第一行输入两个整数n 和m,分别表示银行卡数目和小明记忆中的信息的数目。(1≤n,m≤10000)  接下来 m 行:
 如果每行第一个数是1,接下来有三个整数a,b,c,表示银行卡a 比银行卡b 至少多c 元。  如果每行第一个数是2,接下来有三个整数a,b,c,表示银行卡a 比银行卡b 至多多c 元。  如果每行第一个数是3,接下来有两个整数 a,b,表示银行卡a 和b 里的存款一样多。(1≤n,m,a,b,c≤10000)  输出格式: 如果存在某种情况与小明的记忆吻合,输出Yes,否则输出No。
 样例输入
 3 3
 3 1 2
 1 1 3 1
 2 2 3 2
 样例输出
 Yes

首先讲一下差分约束系统
差分约束系统是最短路的一类经典应用。如果一个不等式组由n个变量和m个约束条件
组成,且每个约束条件都是形如 xi−xj≤k,1≤i,j≤n 的不等式,则称其为 差分约束系统
(system of difference constraints)。差分约束系统是求解一组变量的不等式组的
算法。  问题转化
 我们在求解差分约束系统时,可以将其转化为图论中单源最短路(或最长路)问题。  对于不等式中的其中一组 xj−xi≤k,我们会发现它类似最短路网络(全部由最短路上
的边组成的子图)中的三角不等式 dv−du≤w<u,v>,即 du+w<u,v>≥dv,所以我们
可以理解成从顶点u 到顶点 v 连一条权值为 w<u,v> 的边,用最短路算法得到最短路
的答案 di,也就求出了原不等式组的一个解。  因此我们可以将每个变量xi作为一个顶点,对于约束条件 xj−xi≤k,连接一条边权为
k 的有向边 <i,j>。我们再增加一个超级源s,s 连向其余每个顶点,边权均为0。对这
个图执行单源最短路算法,如果程序正常结束,那么得到的最短路答案数组di 就是满
足条件的一组解;若图中存在负环,则该不等式组无解。

两种连边方法
连边有两种方法,第一种是连边后求最长路的方法,第二种是连边后求最短路
的方法。  例: xj−xi≤k
 若求最短路,则变形为 xi+k≥xj,从i 到j 连一条权值为k 的边。  若求最长路,则变形为 xj−k≤xi,从j 到i 连一条权值为k 的边。  考虑到差分约束系统的边权可能为负,我们套用前面介绍的 SPFA 算法可解决这个问题。

直接上代码

#include <iostream>
#include <string.h>
#include <queue>
using namespace std;

const int MAX_M=10000;
const int MAX_N=1000;
const int inf=0x3f3f3f3f;
int head[MAX_N];
int number_entrance[MAX_N];
int dis[MAX_N];
int inq[MAX_N];
int ans=0;
int n,m;

struct edge{
    int w;
    int to;
    int next;
}eid[MAX_M];

void insert(int u,int v,int w){
    eid[ans].w=w;
    eid[ans].to=v;
    eid[ans].next=head[u];
    head[u]=ans++;
}

int spfa(int start){
    int sum=0;
    int totle=1;
    memset(inq,0, sizeof(inq));
    memset(head,-1, sizeof(head));
    memset(number_entrance,0, sizeof(number_entrance));
    memset(dis,inf, sizeof(dis));

    inq[start]=1;
    number_entrance[start]++;
    dis[start]=0;
    deque<int> q;
    q.push_back(start);
    while(!q.empty()){
        int x=q.front();q.pop_front();

        if(dis[x]*totle>sum){
            q.push_back(x);
            continue;
        }
        //这里采用了LLL优化算法(large lable last)
        sum-=dis[x];
        inq[x]=0;
        totle--;

        for(int i=head[x];i!=-1;i=eid[i].next){
            int y=eid[i].to;
            if(dis[x]+eid[i].w<dis[y]){
                dis[y]=dis[x]+eid[i].w;
                if(inq[y]==0){
                    number_entrance[y]++;
                    inq[y]=1;
                    totle++;
                    sum+=dis[y];
                    if(dis[y]<dis[q.front()]){
                        q.push_front(y);
                    }//这里采用了SLF优化算法(small lable first)
                    else
                        q.push_back(y);

                    if(number_entrance[y]>=n)
                        return 0;
                }
            }
        }
    }
    return  1;
}
int main() {
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        insert(0,i,0);
        insert(i,0,0);
    }

    for(int i=0;i<m;i++){
        int temp;
        cin>>temp;
        if(temp==3){
            int a,b;
            cin>>a>>b;
            insert(a,b,0);
            insert(b,a,0);
        }
        else if(temp==2){
            int a,b,c;
            cin>>a>>b>>c;
            insert(a,b,-c);
        } else{
            int a,b,c;
            cin>>a>>b>>c;
            insert(b,a,c);
        }
    }

    if(spfa(0)==1)
        cout<<"YES";
    else
        cout<<"NO";

    return 0;
}

这里采用的是最短路的算法,所以产生负圈的时候就直接返回0,不能产生满足方程组的变量。

• SLF:Small Label First 策略,设要加入的顶点是 j,队首元素为i,若
d[j]<d[i],则将j 插入队首,否则插入队尾;
• LLL:Large Label Last 策略,设队首元素为i,队列中所有最短距离值的
平均值为x,若 d[i]>x 则将i 插入到队尾,查找下一元素,直到找到某一顶点i 使 得 d[i]≤x,则将i 出队进行松弛操作。 • SLF 可使速度提高 15~ 20%;SLF + LLL 可提高约 50%。  在解决算法题目时,不带优化的 SPFA 就足以解决问题;而一些题目会故意制
造出让 SPFA 效率低下的数据,即使你使用这两个优化也无法避免“被卡”。
因此,SLF 和 LLL 两个优化仅作了解就可以了,在竞赛中不必使用。  对于稀疏图而言,SPFA 相比堆优化的 Dijkstra 有很大的效率提升,但是对于
稠密图而言,SPFA 最坏为 O(VE),远差于堆优化 Dijkstra 的 O((V+E)logV)。
当然,在图中包含负权边时,SPFA 几乎是唯一的选择。因此,大家在做题时,
还是要根据数据的具体情况来判断使用哪种最短路算法。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值