模板
int find(int x) {
return fa[x] == x ? x : fa[x] = find(fa[x]);
}
void join(int x, int y) {
int fx = find(x), fy = find(y);
if (fx != fy) fa[fx] = fy;
}
關(guān)于并查集的一點(diǎn)心得
大家都說帶權(quán)并查集的起點(diǎn)是食物鏈( POJ - 1182 ),但是可能我比較笨,沒有從這道題里領(lǐng)會(huì)到很深的東西,一直到做到蟲子的一生( POJ - 2492 )才有點(diǎn)領(lǐng)悟到,也多虧了在做Navigation Nightmare( POJ - 1984 )時(shí)進(jìn)巨的點(diǎn)撥。
并查集
并查集就是一棵樹,與之相關(guān)的最小生成樹的Kruskal
,從一個(gè)環(huán)圖中找一棵由最短的邊組成的樹,只要一棵,也就是說每個(gè)點(diǎn)都必須至少有一條路可達(dá),這就是最小生成樹。
把關(guān)聯(lián)的結(jié)點(diǎn)用父子關(guān)系鏈接,這樣,有關(guān)的結(jié)點(diǎn)的根節(jié)點(diǎn)就會(huì)是同一個(gè),可以判斷出是否在一棵樹上。同時(shí)可以判環(huán),如果兩個(gè)點(diǎn)已知相連,并且有同一個(gè)根節(jié)點(diǎn),那就是有環(huán)。
并查集的兩個(gè)常用操作,一個(gè)是初始化,所有的點(diǎn)的父節(jié)點(diǎn)都是自己,一個(gè)是查找當(dāng)前結(jié)點(diǎn)的根節(jié)點(diǎn),一個(gè)是合并兩棵樹。查找根節(jié)點(diǎn)一般用遞歸,找到最上方的結(jié)點(diǎn),其父節(jié)點(diǎn)就是自身。為了提高時(shí)間效率,查找時(shí)加上壓縮路徑,即每個(gè)點(diǎn)的父節(jié)點(diǎn)都直接改變成根節(jié)點(diǎn)。合并樹操作在帶權(quán)并查集中發(fā)揮很大作用。
并查集詳解這篇文章講得淺顯易懂,很不錯(cuò)。
帶權(quán)并查集
而帶權(quán)并查集,其中的權(quán)需要轉(zhuǎn)化成子節(jié)點(diǎn)與父節(jié)點(diǎn)之間的聯(lián)系。這樣向上查找時(shí)就能發(fā)現(xiàn)父節(jié)點(diǎn)和子節(jié)點(diǎn)之間的關(guān)系,以此來進(jìn)行計(jì)算。
帶權(quán)并查集的壓縮路徑方法是,在遞歸向上查找的同時(shí),因?yàn)檫f歸是直接到達(dá)最深處然后向上回溯的,所以只需要對每個(gè)點(diǎn)都做一次累加,這樣回到原來的位置時(shí)就是全部的累加。合并樹操作各有不同,主要是創(chuàng)建父節(jié)點(diǎn)的操作。
舉個(gè)例子,蟲子的一生( POJ - 2492 )中用權(quán)數(shù)組表示兩個(gè)蟲子的性別關(guān)系,更新時(shí)就只要考慮一下同性還是異性即可。
G - Supermarket
題解
貪心。
給出一堆商品的收益和必須要賣出的deadline
,求能獲得的最大收益。
WA了好幾次,本來以為自己不會(huì)出這種小bug了,看起來還是編碼能力有待提高。也不是很懂為什么一道簡單貪心放在并查集上。
先把所有商品的兩個(gè)屬性封裝,然后排序,從頭開始找最大的,放在能放的最后面的位置。
代碼
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn = 10005;
struct pord{
int p, d;
}mp[maxn];
int n, vis[maxn];
bool cmp(pord a, pord b){
return a.p > b.p;
}
int main(){
while (~scanf("%d", &n)){
for (int i = 1; i <= n; ++i)
scanf("%d%d", &mp[i].p, &mp[i].d);
sort(mp+1, mp+n+1, cmp);
memset(vis, 0, sizeof(vis));
int ans = 0;
for (int i = 1; i <= n; ++i)
for (int j = mp[i].d; j > 0; --j)
if (!vis[j]){
vis[j] = 1;
ans += mp[i].p;
break;
}
printf("%d\n", ans);
}
return 0;
}
H - Parity game
題解
并查集+離散化。
有一個(gè)01串,1e9位。給出一些子串,和他們中有奇數(shù)個(gè)還是偶數(shù)個(gè)1,求這些中有幾個(gè)是對的。
因?yàn)殚L度太長所以開不下這么多數(shù)組,而子串只有五千個(gè),所以需要哈希一下。只要存最多一萬個(gè)數(shù)字就可以。為了避免不在一起的相鄰所以多存一位,就是兩萬位。然后用一個(gè)數(shù)組存當(dāng)前下標(biāo)之前有奇數(shù)還是偶數(shù)個(gè)1,最后用并查集求解。
代碼
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn = 100005;
int fa[maxn], val[maxn], n, m;
int hashSet[maxn];
struct {
int u, v, w;
}node[maxn];
int find(int n){
int k = fa[n];
if(fa[n] != n){
fa[n] = find(fa[n]);
val[n] = (val[n] + val[k])%2;
}
return fa[n];
}
void init(){
for (int i = 0; i <= n; ++i)
val[i] = 0, fa[i] = i;
}
int main(){
while (~scanf("%d", &n)){
int i, k = 0;
//init放在這里會(huì)RE
scanf("%d", &m);
for (i = 0; i < m; ++i){
char s[5];
scanf("%d%d%s", &node[i].u, &node[i].v, s);
node[i].w = s[0] == 'e'? 0:1;
hashSet[k++] = node[i].u - 1;
hashSet[k++] = node[i].u;
hashSet[k++] = node[i].v - 1;
hashSet[k++] = node[i].v;
}
hashSet[k++] = n;
hashSet[k++] = n - 1;
sort(hashSet, hashSet+k);
n = (int)(unique(hashSet, hashSet+k) - hashSet);
init();
//init放這里就AC
for (i = 0; i < m; ++i){
int u = (int)(lower_bound(hashSet, hashSet+n, node[i].u-1) - hashSet);
int v = (int)(lower_bound(hashSet, hashSet+n, node[i].v) - hashSet);
int fu = find(u), fv = find(v);
if (fu == fv && (val[u] + node[i].w)%2 != val[v])
break;
if (fu < fv){
fa[fv] = fu;
val[fv] = (val[u] + node[i].w - val[v] + 2) % 2;
}
if (fu > fv){
fa[fu] = fv;
val[fu] = (val[v] - node[i].w - val[u] + 2) % 2;
}
}
printf("%d\n", i);
}
return 0;
}
I - Navigation Nightmare
題解
帶權(quán)并查集。
題目給出一些路,路有起點(diǎn)、終點(diǎn)、長度、方向四個(gè)屬性。要求回答提問的兩個(gè)點(diǎn)之間的曼哈頓距離(x軸距離+y軸距離
),注意提問給出了提問的時(shí)間,此時(shí)的地圖不一定完整(不是所有的路都能用)。
卡了很久。都是TLE,因?yàn)橐粋€(gè)地方?jīng)]有把二層循環(huán)改為O(nlogn)
的寫法。
col
數(shù)組存儲(chǔ)當(dāng)前點(diǎn)相對于父節(jié)點(diǎn)的偏移量,ed
數(shù)組存儲(chǔ)邊的四個(gè)屬性,q
數(shù)組存儲(chǔ)問題。之所以用數(shù)組是為了避免因?yàn)閱栴}的順序是亂的,用cmp
函數(shù)排個(gè)序。
find
查找函數(shù)中有路徑壓縮,把樹上的所有點(diǎn)的偏移量直接改為到根節(jié)點(diǎn)的偏移量。
join
合并函數(shù)根據(jù)路的方向和長度來更新偏移量。
最后搜索,如果不在同一棵樹上就不成立,否則維護(hù)偏移量。
代碼
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn = 40005;
int fa[maxn], ans[maxn];
int n, m;
struct{
int x, y;
}col[maxn];
struct {
int x, y, d;
char s[2];
}ed[maxn];
struct que{
int a, b, idx, n;
}q[maxn];
bool cmp(que a, que b) {
return a.idx < b.idx;
}
int find(int n) {
if (n != fa[n]) {
int k = fa[n];
fa[n] = find(fa[n]);
col[n].x += col[k].x;
col[n].y += col[k].y;
}
return fa[n];
}
void join(int u, int v, int i) {
int fu = find(u);
int fv = find(v);
if (fu == fv) return;
fa[fv] = fu;
int rx = col[v].x;
int ry = col[v].y;
switch (ed[i].s[0]){
case 'E':
col[fv].x = col[u].x + ed[i].d - col[v].x;
col[fv].y = col[u].y - col[v].y;
break;
case 'W':
col[fv].x = col[u].x - ed[i].d - col[v].x;
col[fv].y = col[u].y - col[v].y;
break;
case 'S':
col[fv].x = col[u].x - col[v].x;
col[fv].y = col[u].y - ed[i].d - col[v].y;
break;
case 'N':
col[fv].x = col[u].x - col[v].x;
col[fv].y = col[u].y + ed[i].d - col[v].y;
break;
}
}
int main() {
while (~scanf("%d%d", &n, &m)) {
for (int i = 0; i <= n; ++i) fa[i] = i;
for (int i = 1; i <= m; ++i)
scanf("%d%d%d%s", &ed[i].x, &ed[i].y, &ed[i].d, ed[i].s);
int k;
scanf("%d", &k);
for (int i = 1; i <= k; ++i) {
scanf("%d%d%d", &q[i].a, &q[i].b, &q[i].idx); // input questions
q[i].n = i;
}
sort(q + 1, q + k + 1, cmp);
for (int i = 1; i <= k; ++i) {
int cur = i == 1 ? 1 : q[i - 1].idx + 1;
for (int j = cur; j <= q[i].idx; ++j) {
join(ed[j].x, ed[j].y, j);
}
if (find(q[i].a) != find(q[i].b))
ans[q[i].n] = -1;
else
ans[q[i].n] = abs(col[q[i].a].x - col[q[i].b].x) + abs(col[q[i].a].y - col[q[i].b].y);
}
for (int i = 1; i <= k; ++i)
printf("%d\n", ans[i]);
}
return 0;
}
J - A Bug's Life
題解
帶權(quán)并查集。
給出n
條蟲子的m
條交配記錄。假設(shè)蟲子有兩個(gè)性別,都是異性戀。根據(jù)記錄判斷假設(shè)是否正確。
簡單的帶權(quán)并查集應(yīng)用。難點(diǎn)主要在于如何選擇權(quán)數(shù)組的形式。
這里用權(quán)數(shù)組re
來表示當(dāng)前蟲子和上一級(jí)蟲子的關(guān)系,0
為同性,1
為異性。要注意權(quán)數(shù)組意義,以及如何在合并樹操作時(shí)處理權(quán)數(shù)組。
代碼
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn = 2005;
int fa[maxn], re[maxn];
int n, m;
bool flag;
void init(){
for (int i = 0; i <= n; ++i)
fa[i] = i;
memset(re, 0, sizeof(re));
flag = true;
}
int find(int x){
if (x == fa[x])
return x;
int t = find(fa[x]);
re[x] = (re[x] + re[fa[x]]) % 2;
fa[x] = t;
return fa[x];
}
bool join(int x, int y){
int fx = find(x), fy = find(y);
if (fx == fy){
if (re[x] == re[y])
return false;
return true;
}
fa[fy] = fx;
re[fy] = (re[y] - re[x] + 1) %2;
return true;
}
bool solve(){
int a, b;
bool f = true;
scanf("%d%d", &n, &m);
init();
for (int i = 1; i <= m; ++i){
scanf("%d%d", &a, &b);
if (!join(a, b))
f = false;
}
return f;
}
int main(){
int t;
scanf("%d", &t);
for (int i = 1; i <= t; ++i){
if (solve())
printf("Scenario #%d:\nNo suspicious bugs found!\n\n", i);
else
printf("Scenario #%d:\nSuspicious bugs found!\n\n", i);
}
return 0;
}