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 }

 

posted @ 2017-09-13 01:23  Leohh  阅读(174)  评论(0编辑  收藏  举报