POJ 1182 ——食物鏈 經典並查集
- 題意 :有A,B,C三種動物構成環形的食物鏈,現給你M句話,每句話形式"D X Y",1表示X和Y同類,2表示X吃Y。判斷假話有多少句。其中 :1)與前面的話衝突 2)X和Y大於動物編號N 3)D =2 &&X =Y 是假話。
- 思路 :這是一道經典的並查集題目。涉及到並查集,我們就針對這道題目設定並查集中各個元素的含義。 我們將可以確定關係的放在一個集合(即同類或者X 吃/被吃 Y)。rk[i]表示i與根節點的關係 : rk[i] = 0 表示i和根節點是同類 rk[i] = 1 表示根節點吃i rk[i] = 2 表示i吃根節點
一、初始化: node[i] = i , rk[i] = 0。 即每個節點都屬於獨立的集合, 二、find() 函式 :
rk[x] | rk[y] | x與z |
---|---|---|
0 | 0 | 0 |
0 | 1 | 1 |
0 | 2 | 2 |
1 | 0 | 1 |
1 | 1 | 2 |
1 | 2 | 0 |
2 | 0 | 2 |
2 | 1 | 0 |
2 | 2 | 1 |
如何得出上面的表格。不要忘了 只有三種動物A B C,並且關係是個環形。 例如 rk[x] = 1, rk[y] = 1,表示x被y吃,y被z吃,那麼從環形可以得知x與z的關係是2,即x吃z。 三、**merge函式 :**如果兩個節點不是一個集合,就合併。合併的時候fx = find(x), fy = find(y)。如果將node[fx] = fy,即將x所在樹作為y所在樹的子樹,則更新rk[fx]。 對於x和y之間的關係: 1)如果 d = 1,x與y的關係是同類,為0 2)如果 d = 2,x吃y,則x與y的關係是 1 所以x與y的關係是 d-1 rk[fx]更新為fx與fy的關係, 已知 fx 與 x的關係是(3-rk[x]),因為rk[x]是x與fx的關係,用3減去即反過來;x與y的關係是d-1,y與fy的關係是rk[y],所以rk[fx] = d-1 + 3-rk[x] + rk[y]。
四、如何判斷真假 : 每次輸入的d x y,如果是滿足三條規則的後兩條(x或者y大於n;x吃x)則是假話;如果find(x) = find(y),即x和y可以確定關係,則可以判斷 : 1)如果d = 1,rk[x] != rk[y],則是假話。因為 d = 1代表x和y是同類,與根節點的關係相同。 2)如果d = 2,(rk[x] + 1)%3 != rk[y],則是假話。因為d = 2,則x吃y, 當 rk[x] = 0時,rk[y] = 1 當 rk[x] = 1時,rk[y] = 2 當 rk[x] = 2時,rk[y] = 0 所以如果不滿足 x吃y,就是假話。
- 程式碼 :
#include "cstdio"
#include "cmath"
#include "cstring"
#include "iostream"
using namespace std;
#define mem(a,b) memset(a,b,sizeof(a))
#define fori(i,l,u) for(int i = l;i < u;i++)
const int maxn = 1e6 + 6;
int node[maxn];
int rk[maxn];
int n,k;
int d,x,y;
int ans;
int fx,fy;
void init(){
ans = 0;
fori(i, 1, n+1) { //初始化 rk[i]=0代表每個節點獨立
node[i] = i;
rk[i] = 0;
}
}
int find(int x){
int t = 0;
if (x != node[x]) {
t = node[x];
node[x] = find(node[x]);
cout<<"node[x] = "<<node[x]<<" t = "<<t<<endl;
rk[x] = (rk[x] + rk[t]) % 3; //通過 x與y的關係 y與z的關係,計算x與z的關係
}
return node[x];
}
void merge(int x,int y,int d){
int fx = find(x);
int fy = find(y);
node[fx] = fy;
rk[fx] = (d-1 + rk[y] + 3-rk[x])%3;
}
bool check(int d,int x,int y){
bool a = true;
if (x > n || y > n) {
a = false;
}
if (d == 2 && (x == y)) {
a = false;
}
return a;
}
int main()
{
// freopen("1.txt", "r", stdin);
scanf("%d%d",&n,&k);
init();
while (k--) {
scanf("%d%d%d",&d,&x,&y);
if (!check(d, x, y)) {
ans ++;
}
else{
fx = find(x);
fy = find(y);
if (fx == fy) {
if (d == 1 && rk[x] != rk[y]) {
ans++;
}
if (d == 2 && (rk[y]+1)%3 != rk[x]) {
ans++;
}
}
else{
merge(x,y,d);
}
}
}
printf("%d\n",ans);
return 0;
}
複製
- 遇到的問題 : 1)這道題用cin會TLE,我做的並查集的題一般資料大都用scanf。 2)沒有註釋 freopen。。。自閉了。 3)只有一組資料,不要跟hdu似的while ~scanf