BZOJ 1202 [HNOI2005]狡猾的商人:並查集(維護距離)

Leohh發表於2017-09-13

題目連結: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 }

 

相關文章