【洛谷 P7323 [WC2021] 括号路径】解题报告(思维题+并查集)

题面

给定一张 n 个点 2m 条边的有向图,图中的每条边上都有一个标记,代表一个左括号或者右括号。共有 k 种不同的括号类型,即图中可能有 2k 种不同的标记。点、边、括号种类均从 1 开始编号。

图中的每条边都会和另一条边成对出现。更具体地,若图中存在一条标有第 w 种括号的左括号的边 (u,v),则图中一定存在一条标有第 w 种括号的右括号的边 (v,u)。同样地,图中每条标有右括号的边将对应着一条反方向的标有同类型左括号的边。

现在请你求出,图中共有多少个点对 (x,y)1x<yn)满足:图中存在一条从 x 出发到达 y 的路径,且按经过顺序将路径各条边上的标记拼接得到的字符串是一个合法的括号序列。

考场思路

考试那会啥都不会,想直接暴力。定义 oku,v 表示从 uv 有一条路,然后暴力 dfs,重看代码发现貌似思路非常神奇,使我想到一个笑话:

刚刚写下了一段代码,只有我和上帝知道这是什么意思。

(一天后……)

现在只有上帝知道这是什么意思了。

(又一天后……)

上帝:这是什么垃圾玩意!

赛时感觉非常错,但一直没能 Hack 掉,最后出分发现符合预期的 20 分都拿到了。

总之就是个看不懂、证不出、叉不掉的玩意。

题解

我们约定:ab 表示存在一条从 ab 的合法路径,a(b 表示存在一条从 a 指向 b 的左括号边,a)b 表示存在一条从 a 指向 b 的右括号边。

则有:

  • abba,例如 ([{}]{}) 反过来走就是 ({}[{}])。因为一对匹配的括号在反过来以后肯定依然是匹配的。
  • 当两条边权值相等时,b(ac(abc,因为左括号边就意味着一个反向的右括号边。

根据这两条性质,我们可以把所有权值相等且 b(ac(ab,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;
}
posted @   rui_er  阅读(259)  评论(0编辑  收藏  举报
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示