洛谷P2294. [HNOI2005] 狡猾的商人

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

题意简述

\(\qquad\)给定 \(n\) 个数字\(a_1\sim a_n\),给定 \(m\) 组约束关系,其中有三个整数 \(s,t,v\) 表示从第 \(s\) 个月到第 \(t\) 个月的收入为 \(v\), 最后判断 \(a\) 数列与约束关系有没有冲突。

解题思路

\(\qquad\)从前缀和思想我们可以发现,对于约束关系\(\{s, t,v\}\)我们可以转化成这样:

\(\qquad\)\(x[i]\) 表示前 \(i\) 天的收入之和,所以 \([s, t]\)这段时间的收入 \(v\) 可以表示成下面这样:

\[\large x_t - x_{s- 1} = v \]

\(\qquad\) 然后用差分约束系统常用的技巧等于号变成两个不等号可以得到下式

\[\large x_t\le x_{s-1}+v \Rightarrow s-1\to t, w=v \]

\[\large x_{s-1}\le x_t-v\Rightarrow t\to s-1,w=-v \]

\(\qquad\)最后用最短路判断负环即可,有负环代表账单造假输出false,否则输出true

代码

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 110, M = 2020;
int h[N], a[N], n, m, idx;
int dist[N], st[N], ring;

struct Edge 
{
    int cur, ne, w;
} e[M];

void add(int a, int b, int c) 
{
    e[ ++ idx].cur = b, e[idx].ne = h[a];
    e[idx].w = c, h[a] = idx;
}

void dfs(int u) 
{
    st[u] = 1;

    for (int i = h[u]; i; i = e[i].ne) 
    {
        int j = e[i].cur;
        if (dist[j] > dist[u] + e[i].w) 
        {
            dist[j] = dist[u] + e[i].w;
            if (st[j] == 1) 
            {
                ring = true ;
                break ;
            }
            dfs(j);
            if (ring) return ;
        }
    }

    st[u] = 2;
}

int main() 
{
    int T;
    scanf("%d", &T);

    while (T -- ) 
    {
        scanf("%d%d", &n, &m);

        memset(h, 0, sizeof h);
        memset(st, 0, sizeof st);
        idx = ring = 0;

        while (m -- ) 
        {
            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 ++ ) 
        {
            if (ring) break ;
            if (st[i]) continue ;
            dfs(i);
        }

        puts(ring ? "false" : "true");
    }
}

--------------------------分割线-----------------------

\(\qquad\) 值得一提的是这道题也可以用带权并查集来解,定义 \(d[x]\)\(x\) 到所在集合根节点的距离,两点之间的距离应该就是 \(d[x] - d[y]\),合并的时候记得吧父节点的累加到儿子身上。

并查集代码

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 220;
int d[N], p[N], n, m, T, ok;

int find(int x) 
{
    if (x == p[x]) return x;
    int root = find(p[x]);
    d[x] += d[p[x]];
    return p[x] = root;
}

void add(int u, int v, int w) 
{
    int fu = find(u), fv = find(v);

    if (fu != fv) 
    {
        p[fv] = fu;
        d[fv] = d[u] - d[v] + w;
    }
    else if (d[v] - d[u] != w) ok = 0;
}

int main() 
{
    scanf("%d", &T);

    while (T -- ) 
    {
        scanf("%d%d", &n, &m);

        for (int i = 0; i <= n; i ++ ) d[i] = 0, p[i] = i;

        ok = 1;
        while (m -- ) 
        {
            int s, t, v;
            scanf("%d%d%d", &s, &t, &v);
            add(s - 1, t, v);
        }

        puts(ok ? "true" : "false");
    }

    return 0;
}

区间DP

容易得到以下状态表示$$f[l][r]表示[l,r]这段时间的收入$$
\(\qquad\)枚举这段时间的所有断点\(k\in [l,r-1]\),可以把\([l,r]\)区间切割成两部分\([l,k],[k+1,r]\)
然后\(f[l][r] = f[l][k] + f[k + 1][r]\),我们用\(INF\)表示区间营业额没有确定,如果区间营业额已经确定且与两个子区间的和不同,代表冲突,可以输出false

代码

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 220, INF = 0x3f3f3f3f;
int f[N][N], n, m, T, ok;

int main() 
{
    scanf("%d", &T);

    while (T -- ) 
    {
        scanf("%d%d", &n, &m);
        ok = 1;
        
        memset(f, 0x3f, sizeof f);
        while (m -- ) 
        {
            int s, t, v;
            scanf("%d%d%d", &s, &t, &v);
            if (f[s][t] == INF) f[s][t] = v;
            else if (f[s][t] != v) ok = 0;
        }

        if (!ok) 
        {
            puts("false");
            continue ;
        }

        for (int len = 1; len <= n && ok; len ++ ) 
        {
            for (int l = 1, r = l + len - 1; r <= n; l ++, r ++ ) 
            {
                for (int k = l; k < r; k ++ ) 
                {
                    if (f[l][k] != INF && f[k + 1][r] != INF)
                    {
                        if (f[l][r] == INF) f[l][r] = f[l][k] + f[k + 1][r];
                        else if (f[l][r] != f[l][k] + f[k +1][r]) ok = 0;
                    }
                }
            }
        }

        puts(ok ? "true" : "false");
    }
    
    return 0;
}
阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6