【洛谷 P7323 [WC2021] 括號路徑】解題報告(思維題+並查集)
題面
給定一張 \(n\) 個點 \(2m\) 條邊的有向圖,圖中的每條邊上都有一個標記,代表一個左括號或者右括號。共有 \(k\) 種不同的括號型別,即圖中可能有 \(2k\) 種不同的標記。點、邊、括號種類均從 \(1\) 開始編號。
圖中的每條邊都會和另一條邊成對出現。更具體地,若圖中存在一條標有第 \(w\) 種括號的左括號的邊 \((u,v)\),則圖中一定存在一條標有第 \(w\) 種括號的右括號的邊 \((v,u)\)。同樣地,圖中每條標有右括號的邊將對應著一條反方向的標有同類型左括號的邊。
現在請你求出,圖中共有多少個點對 \((x,y)\)(\(1\le x < y\le n\)
考場思路
考試那會啥都不會,想直接暴力。定義 \(ok_{u,v}\) 表示從 \(u\) 到 \(v\) 有一條路,然後暴力 dfs,重看程式碼發現貌似思路非常神奇,使我想到一個笑話:
剛剛寫下了一段程式碼,只有我和上帝知道這是什麼意思。
(一天後……)
現在只有上帝知道這是什麼意思了。
(又一天後……)
上帝:這是什麼垃圾玩意!
賽時感覺非常錯,但一直沒能 Hack 掉,最後出分發現符合預期的 \(20\) 分都拿到了。
總之就是個看不懂、證不出、叉不掉的玩意。
題解
我們約定:\(\def\road#1#2{#1\stackrel{\cdots}{\longrightarrow}#2}\road{a}{b}\) 表示存在一條從 \(a\) 到 \(b\) 的合法路徑,\(\def\lroad#1#2{#1\stackrel{(}{\longrightarrow}#2}\lroad{a}{b}\) 表示存在一條從 \(a\) 指向 \(b\) 的左括號邊,\(\def\rroad#1#2{#1\stackrel{)}{\longrightarrow}#2}\rroad{a}{b}\) 表示存在一條從 \(a\) 指向 \(b\) 的右括號邊。
則有:
- \(\def\road#1#2{#1\stackrel{\cdots}{\longrightarrow}#2}\road{a}{b}\Longleftrightarrow\road{b}{a}\),例如
([{}]{})
反過來走就是({}[{}])
。因為一對匹配的括號在反過來以後肯定依然是匹配的。 - 當兩條邊權值相等時,\(\def\road#1#2{#1\stackrel{\cdots}{\longrightarrow}#2}\def\lroad#1#2{#1\stackrel{(}{\longrightarrow}#2}\lroad{b}{a}\land\lroad{c}{a}\Longrightarrow\road{b}{c}\),因為左括號邊就意味著一個反向的右括號邊。
根據這兩條性質,我們可以把所有權值相等且 \(\def\lroad#1#2{#1\stackrel{(}{\longrightarrow}#2}\lroad{b}{a}\land\lroad{c}{a}\) 的 \(b,c\) 合併到同一個點,同時保留相應的出入邊,這一步可以使用並查集查詢。中間需要合併的點對可以使用一個 waiting queue 暫存,每次取出一對進行合併。
總結一下本題正解:
暴力怎麼做?暴力是不是:加邊!加邊!加邊!然後,並查集查詢。——WC2021 播放的 WC2020 廣告
參考程式碼:
//By: Luogu@rui_er(122461)
#include <bits/stdc++.h>
#define rep(x,y,z) for(ll x=y;x<=z;x++)
#define per(x,y,z) for(ll x=y;x>=z;x--)
#define debug printf("Running %s on line %d...\n",__FUNCTION__,__LINE__)
using namespace std;
typedef long long ll;
const ll N = 3e5+5, M = 2e6+5;
ll n, m, ans;
template<typename T> void chkmin(T &x, T y) {if(x > y) x = y;}
template<typename T> void chkmax(T &x, T y) {if(x < y) x = y;}
struct Edge {
ll u, v, k;
Edge(ll a=0, ll b=0, ll c=0) : u(a), v(b), k(c) {}
}e[M];
map<ll, ll> from[N]; // ) edges to u, or ( edges from u
queue<pair<ll, ll> > wtq; // waiting queue, waiting to merge(first, second)
namespace dsu {
ll fa[N], sz[N];
void init(ll x) {
rep(i, 1, x) {
fa[i] = i;
sz[i] = 1;
}
}
ll find(ll x) {return x == fa[x] ? x : fa[x] = find(fa[x]);}
bool merge(ll x, ll y) {
ll u = find(x), v = find(y);
if(u == v) return 0;
if(from[u].size() > from[v].size()) swap(u, v);
for(map<ll, ll>::iterator it=from[u].begin();it!=from[u].end();it++) {
ll k = it->first, g = it->second;
if(!from[v][k]) from[v][k] = g;
else wtq.push(make_pair(g, from[v][k]));
}
fa[u] = v;
sz[v] += sz[u];
sz[u] = 0;
from[u].clear();
return 1;
}
}
int main() {
scanf("%lld%lld%*d", &n, &m);
dsu::init(n);
rep(i, 1, m) {
ll u, v, k;
scanf("%lld%lld%lld", &u, &v, &k);
e[i] = Edge(u, v, k);
if(!from[v][k]) from[v][k] = u;
else wtq.push(make_pair(u, from[v][k]));
}
while(!wtq.empty()) {
ll u = wtq.front().first, v = wtq.front().second;
wtq.pop();
dsu::merge(u, v);
}
rep(i, 1, n) if(dsu::fa[i] == i) ans += dsu::sz[i] * (dsu::sz[i] - 1) >> 1;
printf("%lld\n", ans);
return 0;
}
博文作者:rui_er。本部落格所有公開(無密碼)博文可根據 CC BY-NC-SA 4.0 協議進行轉載,非公開博文可與作者聯絡獲得許可後轉載。