zoukankan      html  css  js  c++  java
  • 【洛谷7323】[WC2021] 括号路径(加边加边加边,并查集合并)

    点此看题面

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

    等价的缩点

    考虑对于点(x),如果存在两个点(y,z)有相同的连向它,那么对于所有(z)能到达的点(t)(y)必然能通过(y ightarrow z ightarrow 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
    ",t),0;//输出答案
    }
    
    败得义无反顾,弱得一无是处
  • 相关阅读:
    An introduction to parsing text in Haskell with Parsec
    Kafka and ZooKeeper
    Kotlin + Anko for Android
    LibGDX
    OpenCV
    大中型网站技术实践系列
    Go by Example
    Benchmarks for the Top Server-Side Swift Frameworks vs. Node.js
    The Languages and Frameworks You Should Learn in 2017
    RabbitMQ
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/Luogu7323.html
Copyright © 2011-2022 走看看