##链接##
[HNOI2005]狡猾的商人
Time Limit: 10 Sec Memory Limit: 162 MB
Submit: 4027 Solved: 1940
[Submit][Status][Discuss]
Description
刁姹接到一个任务,为税务部门调查一位商人的账本,看看账本是不是伪造的。账本上记录了n个月以来的收入情况,其中第i 个月的收入额为Ai(i=1,2,3…n-1,n), 。当 Ai大于0时表示这个月盈利Ai 元,当 Ai小于0时表示这个月亏损Ai 元。所谓一段时间内的总收入,就是这段时间内每个月的收入额的总和。 刁姹的任务是秘密进行的,为了调查商人的账本,她只好跑到商人那里打工。她趁商人不在时去偷看账本,可是她无法将账本偷出来,每次偷看账本时她都只能看某段时间内账本上记录的收入情况,并且她只能记住这段时间内的总收入。 现在,刁姹总共偷看了m次账本,当然也就记住了m段时间内的总收入,你的任务是根据记住的这些信息来判断账本是不是假的。
Input
第一行为一个正整数w,其中w < 100,表示有w组数据,即w个账本,需要你判断。每组数据的第一行为两个正整数n和m,其中n < 100,m < 1000,分别表示对应的账本记录了多少个月的收入情况以及偷看了多少次账本。接下来的m行表示刁姹偷看m次账本后记住的m条信息,每条信息占一行,有三个整数s,t和v,表示从第s个月到第t个月(包含第t个月)的总收入为v,这里假设s总是小于等于t。
Output
包含w行,每行是true或false,其中第i行为true当且仅当第i组数据,即第i个账本不是假的;第i行为false当且仅当第i组数据,即第i个账本是假的。
Sample Input
2
3 3
1 2 10
1 3 -5
3 3 -15
5 3
1 5 100
3 5 50
1 2 51
Sample Output
true
false
#两种做法#
##1.差分约束##
差分约束,对于x1-x2=k,那么要同时满足x1-x2>=k和x1-x2<=k,即双向都建边,只是权值一正一负。然后跑SPFA判负环,如果无负环,则没有矛盾边,否则存在矛盾
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
const int maxn = 10000 + 100;
int T,n,m;
struct edge {
int u,v,w;
int next;
}e[maxn];
int head[maxn],tot = 0;
int read() {
int x = 0, f = 1;
char ch = getchar();
while(ch < '0' || ch > '9') {
if(ch == '-') f = -1;
ch = getchar();
}
while(ch >= '0' && ch <= '9') {
x = x * 10 + ch - '0';
ch = getchar();
}
return x * f;
}
void add(int u, int v, int w) {
e[++tot] = (edge){u,v,w,head[u]};
head[u] = tot;
}
int vis[maxn],d[maxn];
bool spfa(int x) {
vis[x] = 1;
for(int i = head[x]; i; i = e[i].next) {
int v = e[i].v;
if(d[v] > d[x] + e[i].w) {
d[v] = d[x] + e[i].w;
if(vis[v] || !spfa(v)) return 0;
}
}
vis[x] = 0;
return 1;
}
void work() {
memset(vis,0,sizeof(vis));
memset(d,233333,sizeof(d));
memset(head,0,sizeof(head));
tot = 0; d[0] = 0;
n = read(), m = read();
for(int i = 1; i <= m; i++) {
int u = read(), v = read(), w = read();
add(u-1,v,w);
add(v,u-1,-w);
}
bool p = spfa(0);
if(p) cout<<"true"<<endl;
else cout<<"false"<<endl;
return;
}
int main() {
T = read();
while(T--) work();
return 0;
}
##2.带权并查集##
首先我们研究并查集的本质,发现其实并查集是一棵树,最开始我们的并查集都是一个单点,然后慢慢地联通的,于是我们维护一个神奇的值是从这个点到根节点的差值,我们一会儿再来讨论维护的这个值,现在我们先看这个:
输入了 l,r,num,显然我们现在是l到r的值之和为num,如果我们把一个点一个点的前缀和表示为s,则s[r]−s[l−1]=num,因此我们可以维护一个值表示这个值到他的根的差值,设这个差值数组是sum,而如果l−1这个点和r这个点是联通的,那么显然有num=sum[l−1]−sum[r]因为我们的sum数组表示的是差值,也就是说从根节点到”最左边的节点”的距离减去从l−1到”最左边的节点”的距离的值,那么l−1的这段值如果减去r的这段值如果不等于输入的num,也就是之前输出的内容有冲突,则说明false,如果没有冲突则在这条账目上是对的
如果两个点的fa不同,则需要新维护
首先将v的父亲合并到u的父亲上 f[tv] = tu,
再更新tv的到tu的距离 sum[tv] = sum[u] + w + sum[v]
在find函数中进行合并即可
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int maxn = 1000 + 100;
int n,m;
int f[maxn],sum[maxn];
int read() {
int x = 0, f = 1;
char ch = getchar();
while(ch < '0' || ch > '9') {
if(ch == '-') f = -1;
ch = getchar();
}
while(ch >= '0' && ch <= '9') {
x = x * 10 + ch - '0';
ch = getchar();
}
return x * f;
}
int find(int x) {
if(f[x] == x) return x;
int t = find(f[x]);
sum[x] += sum[f[x]];
f[x] = t;
return f[x];
}
void work() {
int n = read(), m = read(),flag = 0;
for(int i = 0; i <= n; i++) f[i] = i, sum[i] = 0;
for(int i = 1; i <= m; i++) {
int u = read() - 1, v = read(), w = read();
int tu = find(u), tv = find(v);
if(tu == tv) {
if(sum[v] - sum[u] != w) {
flag = 1;
break;
}
}
else {
f[tv] = tu;
sum[tv] = sum[u] + w - sum[v];
}
}
if(flag) printf("false\n");
else printf("true\n");
}
int main() {
int T = read();
while(T--) {
work();
}
}