把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【洛谷7323】[WC2021] 括号路径(加边加边加边,并查集合并)

点此看题面

  • 有一张\(n\)个点\(2m\)条边的有向图,共有\(k\)种括号。
  • 每条边上有一个括号,保证如果存在一条\(u\rightarrow v\)的边,一定存在一条\(v\rightarrow u\)的边,两条边上的括号种类相同、方向相反。
  • 问有多少对点之间存在一条合法括号路径。
  • \(n\le3\times10^5,m\le6\times10^5\)

等价的缩点

考虑对于点\(x\),如果存在两个点\(y,z\)有相同的连向它,那么对于所有\(z\)能到达的点\(t\)\(y\)必然能通过\(y\rightarrow z\rightarrow t\)的路径走到。

也就是说,\(y,z\)能到达的点集是相同的,因此我们可以通过启发式合并的方式,把\(y,z\)缩为一点。

然后发现我们不断重复缩点的过程,则两点之间存在合法括号路径的充要条件就是它们会被缩为一点。

于是我们加边加边加边,并查集合并,这道题就做完了。

具体实现

对于每个点,我们开一个\(map\),表示到这个点的每种类型的左括号边对应的点。由于同种类型的点会被缩成一个,因此只需保存一个点就可以了。

一开始我们先扫一遍所有边,把初始连向每个点相同类型的点合并,以建出\(map\)

注意,这里的合并不需要立即合并信息,只需把它们加入一个队列中,先在并查集上连通,并把度数较小的点的度数加到较大的点上即可,否则可能引发混乱。

接着我们依次取出队列中的元素,枚举度数较小的点中的所有边,找到较大的点中同种类型的边,把这两条边对应的端点合并加入队列。

这样一来就做完了。

代码:\(O(nlognlogk)\)

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 300000
#define M 600000
using namespace std;
int n,m,k,ex[M+5],ey[M+5],ec[M+5],deg[N+5],cnt,u[N+5],v[N+5];
map<int,int> p[N+5];map<int,int>::iterator it;
int f[N+5],c[N+5];I int fa(CI x) {return f[x]?f[x]=fa(f[x]):x;}
class FastIO
{
	private:
		#define FS 100000
		#define tc() (A==B&&(B=(A=FI)+fread(FI,1,FS,stdin),A==B)?EOF:*A++)
		#define D isdigit(c=tc())
		char c,*A,*B,FI[FS];
	public:
		I FastIO() {A=B=FI;}
		Tp I void read(Ty& x) {x=0;W(!D);W(x=(x<<3)+(x<<1)+(c&15),D);}
}F;
I void Union(RI x,RI y)//合并两个点
{
	(x=fa(x))^(y=fa(y))&&(deg[x]<deg[y]&&(swap(x,y),0),++cnt,deg[f[y]=u[cnt]=x]+=deg[v[cnt]=y]);//并查集上连通,更新度数,扔入队列
}
int main()
{
	#define Expand(x,y,c) (p[x].count(c)?(Union(p[x][c],y),0):p[x][c]=y)//如果已有对应类型的边就合并,否则直接存储
	RI i,j,x,y,z;F.read(n),F.read(m),F.read(k);
	for(i=1;i<=m;++i) F.read(ex[i]),F.read(ey[i]),F.read(ec[i]),++deg[ey[i]];//先记录度数
	for(i=1;i<=m;++i) Expand(ey[i],ex[i],ec[i]);//先扫一遍所有边,对每个点建出初始map
	for(i=1;i<=cnt;++i) for(it=p[v[i]].begin();it!=p[v[i]].end();++it) Expand(u[i],it->second,it->first);//枚举每对要合并的点,把小点的边加到大点上
	long long t=0;for(i=1;i<=n;++i) ++c[fa(i)];for(i=1;i<=n;++i) !f[i]&&(t+=1LL*c[i]*(c[i]-1)/2);//记录每个连通块大小,以计算答案
	return printf("%lld\n",t),0;//输出答案
}
posted @ 2021-02-08 16:47  TheLostWeak  阅读(236)  评论(0编辑  收藏  举报