BZOJ 1202 狡猾的商人
Description
***姹接到一个任务,为税务部门调查一位商人的账本,看看账本是不是伪造的。账本上记录了\(n\)个月以来的收入情况,其中第i 个月的收入额为\(A_{i}(i=1,2,3...n-1,n)\) 。当\(A_{i}\)大于\(0\)时表示这个月盈利\(A_{i}\) 元,当 \(A_{i}\)小于\(0\)时表示这个月亏损\(A_{i}\) 元。所谓一段时间内的总收入,就是这段时间内每个月的收入额的总和。 ***姹的任务是秘密进行的,为了调查商人的账本,她只好跑到商人那里打工。她趁商人不在时去偷看账本,可是她无法将账本偷出来,每次偷看账本时她都只能看某段时间内账本上记录的收入情况,并且她只能记住这段时间内的总收入。 现在,***姹总共偷看了\(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
感觉这是一道很好的题,也挺神的。数据结构用的并不高深,是众人皆知的并查集。
首先我们知道一个账本非法当且仅当给出值与推出值不一。这道题最妙的地方就在于他可以用并查集来维护这个推出值。
我们令\(val[i]\)(每个集合的代表元\(val\)为\(0\))表示账本的前缀和\(pre[i]-pre[find(i)]\)的值,那么我们所需要做的就是维护这个\(val\)数组即可。
首先我们将\(find\)函数进行修改,使之可以维护val值:
inline int find(int a)
{
if (father[a] == a) return father[a];
int p = find(father[a]);
val[a] += val[father[a]];
return father[a] = p;
}
之后就是我们的判断与更新。
对于我们的询问\(s,t,v\)。我们首先查询\(s-1\)与\(t\)是否在一个集合内部。如果在,判断\(val[t]-val[s-1]\)是否与\(v\)相等(令\(p=find(t)=find(s-1)\),\(val[t]=per[t]-pre[p]\),\(val[s-1]=pre[s-1]-pre[p]\),\(val[t]-val[s-1]=pre[t]-pre[s-1]\));否则更新答案,\(val[find(s-1)]=val[t]-val[s-1]-v\)(令\(q=val[s-1]\),因为\(val[s-1]=val[t]-v\),且\(val[s-1]-q=val[find(s-1)]\),所以有\(val[find(s-1)]=val[t]-q-c=val[t]-val[s-1]-v\))。
于是这段代码也就出来了:
inline bool work(int a,int b,int c)
{
int p = find(a),q = find(b);
if (p == q) if (val[b] - val[a] != c) return false;
val[p] = val[p] + val[b] - val[a] - c; //维护前缀和
father[p] = q;
return true;
}
整合起来,贴一份完整的:
#include<cstdio>
#include<cstdlib>
using namespace std;
#define maxn 110
int n,m,father[maxn],val[maxn];
inline void init() { for (int i = 0;i <= n;++i) val[i] = 0,father[i] = i; }
inline int find(int a)
{
if (father[a] == a) return father[a];
int p = find(father[a]);
val[a] += val[father[a]];
return father[a] = p;
}
inline bool work(int a,int b,int c)
{
int p = find(a),q = find(b);
if (p == q) if (val[b] - val[a] != c) return false;
val[p] = val[p] + val[b] - val[a] - c;
father[p] = q;
return true;
}
int main()
{
freopen("1202.in","r",stdin);
freopen("1202.out","w",stdout);
int T,i,a,b,c;
scanf("%d",&T);
while (T--)
{
scanf("%d %d",&n,&m);
init();
for (i = 1;i <= m;++i)
{
scanf("%d %d %d",&a,&b,&c);
if (!work(a-1,b,c)) { printf("false\n"); break; }
}
if (i == m + 1) printf("true\n");
}
fclose(stdout); fclose(stdout);
return 0;
}