luogu2294 狡猾的商人 题解

solution1 带权并查集

给出的信息是区间和的形式,搞个前缀和数组 \(a_i\),表示 \([1,i]\) 月的总收益。

假如知道 \([x,y]\) 月的收益与 \([y,z]\) 月的收益,那么就能推出 \([x,z]\) 月的收益。这时候如果后来的 \([x,z]\) 月的信息产生冲突,那么必定是假的。

由于不同区间的信息具有可合并性和传递性,考虑带权并查集。

首先明确 \(a_i\) 此时表示 \([fa(i),i]\) 的和。这是带权并查集的固定套路。

当给出一个区间信息 \((u,v,w)\) 表示 \([u,v]\) 这一段的和为 \(w\) 时,找到 \(x=fa(u-1)\)\(y=fa(v)\)

如果 \(x \neq y\),那么令 \(fa(y)=x\),也就是将这两段区间合并。关键在于如果处理合并后区间的和。

很明确了。更新 \(a_y = a_{u-1}+w-a_v\)

如果上图中 \(x\)\(y\) 的右边也没有问题,只不过 \(a_y\) 是个负数,理论上也是讲得通的。

如果 \(x=y\),那么直接判断 \(a_v - a_{u-1}\) 是否等于 \(w\)

CODE

#include<bits/stdc++.h>
using namespace std;
const int N=105, M=5005;
int T, n, m;
namespace union_set {
    int a[N], f[N];
    int get(int x) {
        if(x==f[x]) return x;
        int t=get(f[x]);
        a[x]+=a[f[x]];
        // 路径压缩必定要做的事
        return f[x]=t;
    } 
    void solve() {
        n=read(), m=read();
        for(int i=0;i<=n;++i) f[i]=i, a[i]=0;
        bool fg=1;
        while(m--) {
            int u=read(), v=read(), w=read();
            int x=get(u-1), y=get(v);
            if(x==y) {
                if(a[v]-a[u-1]!=w) fg=0;
            }
            else f[y]=x, a[y]=a[u-1]-a[y]+w;
        }
        puts(fg? "true":"false");
    }
};

solution2 差分约束系统

这个更显然了。但是给出了是 \([u,v]\) 区间和一定是 \(w\)。那么直接转化 \(X_v - X_{u-1} = z\)\(X_{u-1} - X_v = -z\)。由于是直接等于,那么直接添加双向边add(u-1,v,z)add(v,u-1,-z)

SPFA 跑最短路,判负环。

图不一定连通,那就搞一个超级源点 \(n+1\),对于节点 \(i \in [0,n+1]\),连接add(n+1,i,0)。注意 \(u-1\) 能取到 0,所以不能用 0 当超级源点。

有负环说明账本是假的。

CODE

namespace cfys {
    const int inf=0x3f3f3f3f;
    int dep[N], d[N], v[N];
    int cnt, h[N], to[M], nxt[M], w[M];
    queue<int> q;
    void add(int x,int y,int z) { to[++cnt]=y, w[cnt]=z, nxt[cnt]=h[x], h[x]=cnt; }
    bool SPFA() {
        while(q.size()) q.pop();
        for(int i=0;i<=n+1;++i) d[i]=inf, v[i]=0, dep[i]=0;
        d[n+1]=0, q.push(n+1), v[n+1]=1;
        while(q.size()) {
            int x=q.front(); q.pop();
            v[x]=0;
            for(int i=h[x];i;i=nxt[i]) {
                int y=to[i], z=w[i];
                if(d[y]>d[x]+z) {
                    d[y]=d[x]+z;
                    if(++dep[y]>n+1) return 0;
                    // 0到n一共n+1个节点
                    // 最短路长度超过n+1说明存在负环
                    if(!v[y]) v[y]=1, q.push(y);
                }
            }
        }
        return 1;
    }
    void solve() {
        cnt=0;
        memset(h,0,sizeof(h));
        n=read(), m=read();
        while(m--) {
            int x=read(), y=read(), z=read();
            add(x-1,y,z), add(y,x-1,-z);
        }
        for(int i=0;i<=n;++i) add(n+1,i,0);
        puts(SPFA()? "true":"false");
    }
};

luogu2294 狡猾的商人 题解
https://yozora0908.github.io/2022/lg2294-solution/
作者
yozora0908
发布于
2022年6月30日
许可协议