P7323 [WC2021] 括号路径

# [WC2021] 括号路径

## 题目描述

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

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

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

## 输入格式

第一行三个整数 $n, m, k$,分别表示图中的点数,边对数和括号类型数。

接下来 $m$ 行,每行三个整数 $u, v, w$,表示一条从 $u$ 到 $v$ 的有向边,其标记为第 $w$ 种括号的左括号;以及一条从 $v$ 到 $u$ 的有向边,其标记为第 $w$ 种括号的右括号。

输入给出的图中,任意两个不同的顶点间可以有多条有向边相连,但图中不存在连向自身的有向边,即 $u \ne v$。

## 输出格式

输出仅一行一个整数,表示满足条件的点对数量。

## 样例 #1

### 样例输入 #1

```
4 5 1
4 3 1
4 1 1
4 2 1
1 3 1
2 1 1
```

### 样例输出 #1

```
3
```

## 样例 #2

### 样例输入 #2

```
6 8 2
6 1 2
3 5 1
1 2 2
5 1 2
3 6 2
4 3 1
6 2 2
3 2 1
```

### 样例输出 #2

```
10
```

## 样例 #3

### 样例输入 #3

```
见附件中的 bracket/bracket3.in
```

### 样例输出 #3

```
见附件中的 bracket/bracket3.ans
```

## 样例 #4

### 样例输入 #4

```
见附件中的 bracket/bracket4.in
```

### 样例输出 #4

```
见附件中的 bracket/bracket4.ans
```

## 提示

**【样例解释 #1】**

符合条件的点对及其对应的路径为:

$(1, 2)$:$1 \to 3 \to 4 \to 1 \to 2$。  
$(1, 4)$:$1 \to 3 \to 4$。  
$(2, 4)$:$2 \to 1 \to 4$。

**【数据范围】**

对于所有测试点:$1 \le n \le 3 \times {10}^5$,$1 \le m \le 6 \times {10}^5$,$1 \le k, u, v \le n$,$1 \le w \le k$。

每个测试点的具体限制见下表:

#include<bits/stdc++.h>
#define N 300050
#define ll long long
using namespace std;
map<int, int> mp[N];
queue<pair<int, int> > q;
int fa[N];
int get(int x) {
    return fa[x] == x? x : (fa[x] = get(fa[x]));
}
void merge(int x, int y) {
    x = get(x), y = get(y);
    if(x == y) return;
    if(mp[x].size() > mp[y].size()) swap(x, y);
    for(auto it : mp[x]) {
        if(mp[y][it.first]) q.push(make_pair(mp[y][it.first], it.second));
        else mp[y][it.first] = it.second;
    }
    fa[x] = y; mp[x].clear();
}
void solve() {
    while(q.size()) {
        int x = q.front().first, y = q.front().second; q.pop();
        //printf("%d %d\n", x, y);
        merge(x, y);
    }
}
int n, m, k, gs[N];
int main() {
    scanf("%d%d%d", &n, &m, &k);
    for(int i = 1; i <= n; i ++) fa[i] = i;
    for(int i = 1; i <= m; i ++) {
        int u, v, c;
        scanf("%d%d%d", &u, &v, &c), swap(u, v);
        if(!mp[u][c]) mp[u][c] = v;
        else q.push(make_pair(mp[u][c], v));
    }
    solve();
    for(int i = 1; i <= n; i ++) gs[get(i)] ++;
    ll ans = 0;
    for(int i = 1; i <= n; i ++) if(get(i) == i) {
        ans += 1ll * gs[i] * (gs[i] - 1) / 2;
    }
    printf("%lld", ans);
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

内测人员

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值