「WC2021」括号路径

题解

这道题有两个非常重要的性质需要发现 其实挺显然的,不过好像大多人发现了没反应过来?
1. 1. 1. ( u , v ) (u,v) (u,v) 是合法序列,那么 ( v , u ) (v,u) (v,u) 也是合法序列。
2. 2. 2. ( u , v ) , ( v , k ) (u,v),(v,k) (u,v),(v,k) 都是合法序列,那么 ( u , k ) (u,k) (u,k) 也是合法序列 ( u ≠ v ≠ k ) (u \neq v \neq k) (u=v=k)

根据性质 2 2 2 可以发现:若 i i i j j j 能构成合法路径,那么 i i i j j j 能构成合法路径的点也一定能构成合法路径,相当于把 i i i 合并到 j j j 所在的集合里面。

综上,我们只需要每次将两个指向同一个集合的两个点所在的集合合并即可,合并时遵循启发式合并,效率即可优化到 O ( n l o g n ) O(n logn) O(nlogn)

题解

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+2e5+10;
inline int read(){
	int k=0,f=1;char c=getchar();
	while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
	while(c>='0'&&c<='9')k=k*10+c-'0',c=getchar();
	return k*f;
}
int n,m,k;
int fa[N],num[N];
map<int,int> e[N];
vector<int> col[N];
struct Node{int x,y,w,fst;};
inline int get(int x){return (x==fa[x])?x:fa[x]=get(fa[x]);}
queue<Node> q;
inline void add(int x,int y,int w){
	int kp=e[x][w];
	if(kp==y)return;
	if(!kp)e[x][w]=y,col[x].push_back(w);
	else q.push((Node){kp,y,w,x});
}
long long ans;
int main(){
	n=read();m=read();k=read();
	for(int i=1;i<=n;++i)fa[i]=i,num[i]=1;
	for(int i=1;i<=m;++i){
		int u=read(),v=read(),w=read();
		add(v,u,w);
	}
	while(q.size()){
		int x=q.front().x,y=q.front().y,w=q.front().w,fst=q.front().fst;q.pop();
		if(get(x)==get(y))continue;
		x=get(x);y=get(y);
		if(col[x].size()>col[y].size())swap(x,y);
		e[fst][w]=y;
		for(int j=0;j<(int)col[x].size();++j){
			int ver=col[x][j];
			add(y,e[x][ver],ver);
		}
		num[y]+=num[x];fa[x]=y;
	}
	for(int i=1;i<=n;++i)if(get(i)==i)ans+=1ll*num[i]*(num[i]-1)/2;
	printf("%lld\n",ans);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值