[HNOI 2005]狡猾的商人
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
3 3
1 2 10
1 3 -5
3 3 -15
5 3
1 5 100
3 5 50
1 2 51
Sample Output
false
题解
解法一 差分约束
首先,我们令$sum_i$表示前$i-1$个月的收入和。
对于输入的三元组$(u,v,c)$,显然就是$sum_{v+1}-sum_u == c$,等价于$sum_{v+1}-sum_u >= c$ && $sum_{v+1}-sum_u <= c$,就变成的差分约束的模板。
等于说我们就加上$(u,v+1,c)$和$(v+1,u,-c)$这两条边,判断这个差分约束系统是否有解。
1 //It is made by Awson on 2017.10.15 2 #include <set> 3 #include <map> 4 #include <cmath> 5 #include <ctime> 6 #include <cmath> 7 #include <stack> 8 #include <queue> 9 #include <vector> 10 #include <string> 11 #include <cstdio> 12 #include <cstdlib> 13 #include <cstring> 14 #include <iostream> 15 #include <algorithm> 16 #define LL long long 17 #define Min(a, b) ((a) < (b) ? (a) : (b)) 18 #define Max(a, b) ((a) > (b) ? (a) : (b)) 19 #define sqr(x) ((x)*(x)) 20 using namespace std; 21 const int N = 100; 22 const int M = 1000; 23 24 int n, m; 25 struct tt { 26 int to, next, cost; 27 }edge[(M<<1)+5]; 28 int path[N+5], top; 29 bool vis[N+5]; 30 int dist[N+5]; 31 32 bool dfs_SPFA(int u) { 33 vis[u] = 1; 34 for (int i = path[u]; i; i = edge[i].next) 35 if (dist[edge[i].to] > dist[u]+edge[i].cost) { 36 if (vis[edge[i].to]) {vis[u] = 0; return true;} 37 dist[edge[i].to] = dist[u]+edge[i].cost; 38 if (dfs_SPFA(edge[i].to)) {vis[u] = 0; return true;} 39 } 40 vis[u] = 0; 41 return false; 42 } 43 void add(int u, int v, int c) { 44 edge[++top].to = v; 45 edge[top].next = path[u]; 46 edge[top].cost = c; 47 path[u] = top; 48 } 49 void work() { 50 memset(dist, 0, sizeof(dist)); 51 memset(path, 0, sizeof(path)); top = 0; 52 scanf("%d%d", &n, &m); n++; 53 for (int u, v, c, i = 1; i <= m; i++) { 54 scanf("%d%d%d", &u, &v, &c); 55 add(u, v+1, c); add(v+1, u, -c); 56 } 57 for (int i = 1; i <= n; i++) if (dfs_SPFA(i)) { 58 printf("false\n"); return; 59 } 60 printf("true\n"); 61 } 62 int main() { 63 int t; scanf("%d", &t); 64 while (t--) work(); 65 return 0; 66 }
解法二 带权并查集
我们令$cost_u$表示$u$这个节点到根节点的花销。
对于输入$(u,v,c)$,令$p = root_u$,$q = root_v$。
如果$p != q$即$u$,$v$不在一棵树上,那么我们将$father_p ← q$,此时$cost[p] = cost[v]-c-cost[u]$;
若$p == q$即在同一棵树树上,那么我们只要判断$cost[v]-cost[u] == c$即可。若是,则满足,否则为$false$。
1 //It is made by Awson on 2017.10.15 2 #include <set> 3 #include <map> 4 #include <cmath> 5 #include <ctime> 6 #include <cmath> 7 #include <stack> 8 #include <queue> 9 #include <vector> 10 #include <string> 11 #include <cstdio> 12 #include <cstdlib> 13 #include <cstring> 14 #include <iostream> 15 #include <algorithm> 16 #define LL long long 17 #define Min(a, b) ((a) < (b) ? (a) : (b)) 18 #define Max(a, b) ((a) > (b) ? (a) : (b)) 19 #define sqr(x) ((x)*(x)) 20 using namespace std; 21 const int N = 100; 22 const int M = 1000; 23 24 int n, m; 25 #define set SET 26 #define cost COST 27 int set[N+5], cost[N+5]; 28 int find_father(int r) { 29 if (set[r]) { 30 int t = find_father(set[r]); 31 cost[r] += cost[set[r]]; set[r] = t; 32 return t; 33 } 34 return r; 35 } 36 37 void work() { 38 memset(set, 0, sizeof(set)); 39 memset(cost, 0, sizeof(cost)); 40 scanf("%d%d", &n, &m); 41 while (m--) { 42 int u, v, c; 43 scanf("%d%d%d", &u, &v, &c); v++; 44 int p = find_father(u); 45 int q = find_father(v); 46 if (p != q) { 47 set[p] = q; 48 cost[p] = cost[v]-c-cost[u]; 49 }else { 50 if (cost[v]-cost[u] != c) { 51 while (m--) scanf("%d%d%d", &u, &v, &c); 52 printf("false\n"); return; 53 } 54 } 55 } 56 printf("true\n"); 57 } 58 int main() { 59 int t; scanf("%d", &t); 60 while (t--) work(); 61 return 0; 62 }