洛谷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\\) 可以表示成下面这样:
\\(\\qquad\\) 然后用差分约束系统常用的技巧等于号变成两个不等号
可以得到下式
\\(\\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] 狡猾的商人的主要内容,如果未能解决你的问题,请参考以下文章