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