【洛谷 P7323 [WC2021] 括号路径】解题报告(思维题+并查集)
作者:互联网
题面
给定一张 \(n\) 个点 \(2m\) 条边的有向图,图中的每条边上都有一个标记,代表一个左括号或者右括号。共有 \(k\) 种不同的括号类型,即图中可能有 \(2k\) 种不同的标记。点、边、括号种类均从 \(1\) 开始编号。
图中的每条边都会和另一条边成对出现。更具体地,若图中存在一条标有第 \(w\) 种括号的左括号的边 \((u,v)\),则图中一定存在一条标有第 \(w\) 种括号的右括号的边 \((v,u)\)。同样地,图中每条标有右括号的边将对应着一条反方向的标有同类型左括号的边。
现在请你求出,图中共有多少个点对 \((x,y)\)(\(1\le x < y\le n\))满足:图中存在一条从 \(x\) 出发到达 \(y\) 的路径,且按经过顺序将路径各条边上的标记拼接得到的字符串是一个合法的括号序列。
考场思路
考试那会啥都不会,想直接暴力。定义 \(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;
}
标签:wtq,洛谷,括号,ll,查集,WC2021,fa,lroad,longrightarrow 来源: https://www.cnblogs.com/ruierqwq/p/LG-P7323.html