BZOJ 1202 [HNOI2005]狡猾的商人:并查集(维护距离)
题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=1202
题意:
有一个账本,记录了n个月的盈亏。
每个月的数值:正为盈,负为亏。
你知道m个这个账本的区间和[x[i],y[i]]。
问你这个账本是真是假。
题解:
如果已知区间和[a,b],[b,c],那么就可以算出区间和[a,c]。
而唯一判断账本真假的方法,就是看有没有某个给定的区间和A[a,c]与推出来的区间和B[a,c]不相等。如果不相等,账本为假。
我们常用前缀和来处理区间和。
但此题仅给出点与点之间的差值,因此要用并查集维护:
某一连通块内各节点到根节点的差值dis[i]。
如果两点a,b在同一连通块内,则区间和为dis[b] - dis[a-1]。
根节点dis = 0.
每次要用到某个dis[x]时,要在之前执行一遍find(x),以更新x的真正父亲,也顺便将dis[x]改为了它与真父亲的差值。
让px认py作爹时,要更新dis[px] = px到py的差值 = dis[y]-w-dis[x].
特别要注意前缀和方向:
“从根到节点的前缀和方向”与“认爹箭头方向”相反。
AC Code:
1 #include <iostream> 2 #include <stdio.h> 3 #include <string.h> 4 #define MAX_N 105 5 6 using namespace std; 7 8 int n,m,t; 9 int dis[MAX_N]; 10 int par[MAX_N]; 11 bool flag; 12 13 void init_union_find() 14 { 15 memset(dis,0,sizeof(dis)); 16 for(int i=0;i<=n;i++) 17 { 18 par[i]=i; 19 } 20 } 21 22 int find(int x) 23 { 24 if(par[x]==x) return x; 25 int t=find(par[x]); 26 dis[x]+=dis[par[x]]; 27 return par[x]=t; 28 } 29 30 void unite(int x,int y,int w) 31 { 32 int px=find(x); 33 int py=find(y); 34 if(px==py) return; 35 dis[px]=dis[y]-w-dis[x]; 36 par[px]=py; 37 } 38 39 bool same(int x,int y) 40 { 41 return find(x)==find(y); 42 } 43 44 void work() 45 { 46 cin>>n>>m; 47 int x,y,w; 48 flag=true; 49 init_union_find(); 50 for(int i=0;i<m;i++) 51 { 52 cin>>x>>y>>w; 53 if(same(x-1,y)) 54 { 55 if(dis[y]-dis[x-1]!=w) 56 { 57 flag=false; 58 return; 59 } 60 } 61 else unite(x-1,y,w); 62 } 63 } 64 65 void print() 66 { 67 if(flag) cout<<"true"<<endl; 68 else cout<<"false"<<endl; 69 } 70 71 int main() 72 { 73 cin>>t; 74 while(t--) 75 { 76 work(); 77 print(); 78 } 79 }