BASIC
引入:条件中给出了两两关系,且关系具有传递性,最后问某两是否有关系。
想法:让有关系的抱团,均指向团长。查询时,看是否是同一个团长即可。
基本操作:
*1. 初始化:每个人都是自己的团长。
for(int i = 0;i < n;i++)
p[i] = i;
*2. 找团长:存在长链时,a指向的只是它的熟人,并不是这个团的团长。a想打破垄断,越级走访。顺带着链上的所有人都找到了团长。
int get(int a){
if(p[a] != a) p[a] = get(p[a]);
return p[a];
}
*3. 询问:a和b是不是同一个团的?问问团长!
bool ask(int a, int b){
return get(a) == get(b);
}
*4. 合并:商量好了要团结,搞个大新闻,先联系自己团的团长再联合。
void merge(int a, int b){
int x = get(a), y = get(b);
p[x] = y;
//有时可以加上资源的合并
}
EXERCISES
POJ 1611 感染病毒
先来切个菜。求与编号0一起被感染的人有多少个。
#include <cstdio>
using namespace std;
#define N 30010
int p[N], a[N], n, m, num[N];
int get(int x){
if(x != p[x]) p[x] = get(p[x]);
return p[x];
}
void merge(int x, int y){
int s = get(x), r = get(y);
if(s == r) return;
p[s] = r;
a[r] += a[s];
}
int main(){
while(scanf("%d%d", &n, &m)){
if(!n && !m) break;
for(int i = 0;i < n+1;i++){
p[i] = i;
a[i] = 1;
}
while(m--){
int k;
scanf("%d", &k);
for(int i = 0;i < k;i++)
scanf("%d", &num[i]);
for(int i = 1;i < k;i++)
merge(num[i], num[0]);
}
printf("%d\n", a[get(0)]);
}
return 0;
}
POJ 1988 积木移动
合理构造有助于求解。每次移动以后,让最底下的积木作为团长。
p[i]: i所在堆的团长,初始为i
a[i]: i底下有多少个积木,初始为0
s[i]: i为团长时,表示这堆积木的总数,初始为1
每次get时,也要更新a的值,让a[i]为i的熟人到新团长的链上的a[j]之和,不含新团长。
“该链上只有i,老团长,新团长,所以j就是老团长这一个”,对吗?
int get(int x){
if(p[x] != x){
a[x] += a[p[x]];
p[x] = get(p[x]);
}
return p[x];
}
事实上,多次merge可能会造成长链,上面的写法是错滴!比较好的递归写法如下:
int get(int x){
if(p[x] != x){
int t = get(p[x]);
a[x] += a[p[x]];
p[x] = t;
}
return p[x];
}
就差一丢丢,先后次序决定了p[x]已经被先行处理,a[p[x]]值发生了改变。写不出来的话,还有再傻一点的写法如下:
int get(int x){
if(p[x] != x){
int t = get(p[x]), y = x;
while(p[y] != t){
a[x] += a[p[y]];
y = p[y];
}
p[x] = t;
}
return p[x];
}
会造成长链的重复计算,虽然没有超时,但不是最佳写法。
#include <cstdio>
using namespace std;
#define N 30010
int p[N], a[N], s[N];
int get(int x){
if(p[x] != x){
int t = get(p[x]), y = x;
while(p[y] != t){
a[x] += a[p[y]];
y = p[y];
}
p[x] = t;
}
return p[x];
}
void merge(){
int x, y;
scanf("%d%d", &x, &y);
int xp = get(x), yp = get(y);
if(xp == yp) return;
p[xp] = yp;
a[xp] += s[yp];
s[yp] += s[xp];
}
void ask(){
int x;
scanf("%d", &x);
p[x] = get(x);
printf("%d\n", a[x]);
}
int main(){
int T;
while(~scanf("%d", &T)){
for(int i = 0;i < N;i++){
p[i] = i;
a[i] = 0;
s[i] = 1;
}
while(T--){
char str[10];
scanf("%s", str);
if(str[0] == 'M')
merge();
else
ask();
}
}
return 0;
}
睡个好觉,好题再续~