洛谷P2294. [HNOI2005] 狡猾的商人
2023-04-18 15:18:54 时间
题意简述
(qquad)给定 (n) 个数字(a_1sim 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_tle x_{s-1}+v Rightarrow s-1 o t, w=v
]
[large x_{s-1}le x_t-vRightarrow t o 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)枚举这段时间的所有断点(kin [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;
}
相关文章
- 【技术种草】cdn+轻量服务器+hugo=让博客“云原生”一下
- CLB运维&运营最佳实践 ---访问日志大洞察
- vnc方式登陆服务器
- 轻松学排序算法:眼睛直观感受几种常用排序算法
- 十二个经典的大数据项目
- 为什么使用 CDN 内容分发网络?
- 大数据——大数据默认端口号列表
- Weld 1.1.5.Final,JSR-299 的框架
- JavaFX 2012:彻底开源
- 提升as3程序性能的十大要点
- 通过凸面几何学进行独立于边际的在线多类学习
- 利用行动影响的规律性和部分已知的模型进行离线强化学习
- ModelLight:基于模型的交通信号控制的元强化学习
- 浅谈Visual Source Safe项目分支
- 基于先验知识的递归卡尔曼滤波的代理人联合状态和输入估计
- 结合网络结构和非线性恢复来提高声誉评估的性能
- 最佳实践丨云开发CloudBase多环境管理实践
- TimeVAE:用于生成多变量时间序列的变异自动编码器
- 具有线性阈值激活的神经网络:结构和算法
- 内网渗透之横向移动 -- 从域外向域内进行密码喷洒攻击