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

Posted StkOvflow

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了洛谷P2294. [HNOI2005] 狡猾的商人相关的知识,希望对你有一定的参考价值。

题意简述

\\(\\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;

以上是关于洛谷P2294. [HNOI2005] 狡猾的商人的主要内容,如果未能解决你的问题,请参考以下文章

洛谷 [p2294] [HNOI2005] 狡猾的商人

P4878 [USACO05DEC]Layout G+P2294 [HNOI2005]狡猾的商人

[HNOI2005]狡猾的商人 ,神奇做法——贪心

[HNOI2005]狡猾的商人

[luogu P2294] [HNOI2005]狡猾的商人

[HNOI2005]狡猾的商人