並查集擴充套件應用
A. 貨物運輸
題目描述
有 \(n\) 座城市和 \(m\) 條雙向道路。已知走過每條邊所需要的汽油量,\(q\) 次詢問,求汽油量為 \(l\) 的車可以在多少對城市之間運送貨物。(汽車到達城市會立刻把油全部加滿)
題解
這道題沒有強制線上,所以可以考慮進行離線。
對於大小為 \(n\) 一個連通塊,兩兩相連的點對一共有 \(\frac{n(n-1)}{2}\)
於是 很難不想到 使用並查集維護連通塊的大小,動態更新答案。
# include <bits/stdc++.h>
using namespace std;
using namespace IOS;
typedef long long ll;
# define int long long
# define lc u << 1
# define rc u << 1 | 1
const int N = 100005;
int n, m, q;
struct edge
{
int u, v, w;
bool operator < (const edge &t) const { return w < t.w; }
} e[N];
struct query
{
int l, id;
bool operator < (const query &t) const { return l < t.l; }
} Q[N];
int fa[N], sz[N];
int find_fa (int u) { return u == fa[u] ? u : fa[u] = find_fa (fa[u]); }
int ans;
void union_fa (int u, int v)
{
u = find_fa (u), v = find_fa (v);
if (u != v)
{
fa[v] = u;
ans -= sz[u] * (sz[u] - 1) + sz[v] * (sz[v] - 1);
sz[u] += sz[v];
ans += sz[u] * (sz[u] - 1);
}
}
int res[N];
signed main ()
{
read (n, m, q);
for (int i = 1; i <= m; i ++ ) read (e[i].u, e[i].v, e[i].w);
sort (e + 1, e + 1 + m);
for (int i = 1; i <= q; i ++ )
{
read (Q[i].l);
Q[i].id = i;
}
sort (Q + 1, Q + 1 + q);
for (int i = 1; i <= n; i ++ ) fa[i] = i, sz[i] = 1;
for (int i = 1, j = 1; i <= q; i ++ )
{
while (j <= m && e[j].w <= Q[i].l) union_fa (e[j].u, e[j].v), j ++ ;
res[Q[i].id] = ans;
}
for (int i = 1; i <= q; i ++ ) write (res[i] / 2, '\n');
return 0;
}
B. 銀河英雄傳說
題目描述
yyc 在一次戰鬥中,他將 1233 戰場劃分成 \(30000\) 列,每列依次編號為 \(1, 2,\ldots ,30000\)。之後,他把自己的戰艦也依次編號為 \(1, 2, \ldots , 30000\),讓第 \(i\) 號戰艦處於第 \(i\) 列。這是初始陣形。當進犯之敵到達時,yyc 會多次釋出合併指令。合併指令為 M i j
,含義為第 \(i\) 號戰艦所在的整個戰艦佇列,作為一個整體(頭在前尾在後)接至第 \(j\) 號戰艦所在的戰艦佇列的尾部。顯然戰艦佇列是由處於同一列的一個或多個戰艦組成的。合併指令的執行結果會使佇列增大。
然而,cdx 可以透過龐大的情報網路隨時監聽 yyc 的艦隊調動指令。
在 yyc 釋出指令調動艦隊的同時,cdx 為了及時瞭解當前 yyc 的戰艦分佈情況,也會發出一些詢問指令:C i j
。該指令意思是,詢問電腦,yyc 的第 \(i\) 號戰艦與第 \(j\) 號戰艦當前是否在同一列中,如果在同一列中,那麼它們之間佈置有多少戰艦。
題解
\(fa_i\) 表示飛船 \(i\) 所在列的隊頭。
\(d_i\) 表示飛船 \(i\) 到其所在佇列隊頭的距離,則飛船 \(i\)和飛船 \(j\) 之間的飛船數量 \(|d_i-d_j|-1\)。
\(sz_i\) 表示飛船 \(i\) 所在的佇列的大小
使用並查集維護即可。
# include <bits/stdc++.h>
using namespace std;
using namespace IOS;
typedef long long ll;
//# define int long long
# define lc u << 1
# define rc u << 1 | 1
const int N = 30005;
int m;
int fa[N], sz[N], d[N];
int find_fa (int u)
{
if (u == fa[u]) return fa[u];
int v = find_fa (fa[u]);
d[u] += d[fa[u]];
fa[u] = v;
return v;
}
void union_fa (int u, int v)
{
u = find_fa (u), v = find_fa (v);
if (u != v) fa[u] = v, d[u] = sz[v], sz[v] += sz[u];
}
signed main ()
{
for (int i = 1; i < N; i ++ ) fa[i] = i, sz[i] = 1;
read (m);
while (m -- )
{
char op; int u, v; read (op, u, v);
if (op == 'M') union_fa (u, v);
else
{
int fu = find_fa (u), fv = find_fa (v);
if (fu != fv) write (-1, '\n');
else write (max (0, abs (d[u] - d[v]) - 1), '\n');
}
}
return 0;
}
C. 羅馬遊戲
題目描述
監獄裡面有 \(n\) 個人,每個人都是一個獨立的團。最近統計了每個人善良程度。獄卒可以發兩種命令:
Merge(i, j)
。把 \(i\) 所在的團和j所在的團合併成一個團。如果 \(i, j\) 有一個人是死人,那麼就忽略該命令。Kill(i)
。把 \(i\)所在的團裡面善良程度最低的人殺死。如果 \(i\) 這個人已經死了,這條命令就忽略。 獄卒希望他每釋出一條kill
命令,下面的刀斧手就把被殺的人的善良程度報上來。(如果這條命令被忽略,那麼就報 \(0\))
題解
啟發式合併+並查集
# include <bits/stdc++.h>
using namespace std;
using namespace IOS;
typedef long long ll;
//# define int long long
# define lc u << 1
# define rc u << 1 | 1
const int N = 1000005;
int n, q;
bool dead[N];
int fa[N], sz[N];
priority_queue <int, vector <int>, greater <int> > grp[N];
int find_fa (int u) { return u == fa[u] ? u : fa[u] = find_fa (fa[u]); }
void union_fa (int u, int v)
{
u = find_fa (u), v = find_fa (v);
if (u != v)
{
if (sz[u] < sz[v]) swap (u, v);
fa[v] = u;
while (!grp[v].empty ()) grp[u].push (grp[v].top ()), grp[v].pop ();
sz[u] += sz[v];
}
}
map <int, int> mp;
signed main ()
{
read (n);
for (int i = 1; i <= n; i ++ ) fa[i] = i, sz[i] = 1;
for (int i = 1; i <= n; i ++ )
{
int x; read (x);
grp[i].push (x);
mp[x] = i;
}
read (q);
while (q -- )
{
char c; int x, y; read (c, x);
if (c == 'M')
{
read (y);
if (dead[x] || dead[y]) continue;
union_fa (x, y);
}
else
{
if (dead[x]) { write (0, '\n'); continue; }
x = find_fa (x);
int u = grp[x].top (); grp[x].pop ();
dead[mp[u]] = 1;
write (u, '\n');
}
}
return 0;
}
D. 矩陣
題目描述
有一個 \(n\times m\)的矩陣,初始每個格子的權值都為 \(0\),可以對矩陣執行兩種操作:
- 選擇一行,該行每個格子的權值加 \(1\) 或減 \(1\)。
- 選擇一列,該列每個格子的權值加 \(1\) 或減\(1\)。
現在有 \(K\) 個限制,每個限制為一個三元組 \((x,y,c)\),代表格子 \((x,y)\) 權值等於\(c\)。
問是否存在一個操作序列,使得操作完後的矩陣滿足所有的限制。
如果存在輸出 Yes
,否則輸出 No
。
題解
定義橫著是加,豎著是減。那麼每個限制可以轉化為:\(A_x-A_y=c\)(\(A\) 增加量或減少量)
帶權並查集維護的是到根節點的距離,那麼這個也可以類比到根節點的距離。
\(A_x-A_y=c\) 轉化為 \(A_x=A_y+c\)。
那麼每次修改的其實就是到根節點的距離。
# include <bits/stdc++.h>
using namespace std;
using namespace IOS;
typedef long long ll;
//# define int long long
# define lc u << 1
# define rc u << 1 | 1
const int N = 2005;
int n, m, q;
int fa[N], d[N];
int find_fa (int u)
{
if (u == fa[u]) return u;
int v = find_fa (fa[u]);
d[u] += d[fa[u]];
return fa[u] = v;
}
signed main ()
{
int T; read (T);
while (T -- )
{
read (n, m, q);
for (int i = 1; i <= n + m; i ++ ) fa[i] = i, d[i] = 0;
int ans = 1;
while (q -- )
{
int u, v, w; read (u, v, w);
v += n;
int fu = find_fa (u), fv = find_fa (v);
if (fu != fv)
{
fa[fu] = fv;
d[fu] = d[v] - d[u] + w;
}
else if (d[u] - d[v] != w) ans = 0;
}
if (ans) puts("Yes");
else puts("No");
}
return 0;
}