LG P7323 [WC2021] 括号路径
Description
给定一张 $n$ 个点 $2m$ 条边的有向图,图中的每条边上都有一个标记,代表一个左括号或者右括号。共有 $k$ 种不同的括号类型,即图中可能有 $2k$ 种不同的标记。点、边、括号种类均从 $1$ 开始编号。
图中的每条边都会和另一条边成对出现。更具体地,若图中存在一条标有第 $w$ 种括号的左括号的边 $(u,v)$,则图中一定存在一条标有第 $w$ 种括号的右括号的边 $(v,u)$。同样地,图中每条标有右括号的边将对应着一条反方向的标有同类型左括号的边。
现在请你求出,图中共有多少个点对 $(x,y),1 \leq x \le y \leq n$满足:图中存在一条从 $x$ 出发到达 $y$ 的路径,且按经过顺序将路径各条边上的标记拼接得到的字符串是一个合法的括号序列。
Solution
发现如果路径$S$合法,那在$S$两侧加上一对相同类型的括号之后仍然合法
并且如果$a \rightarrow b$和$b \rightarrow c$合法,那么$a \rightarrow c$同样合法
$a \rightarrow b$合法,对于所有的$c$,若$a \rightarrow c$合法则$b \rightarrow c$合法
所以原图构成多个团,统计每个团的大小,每个团中的任意点对构成一组答案
用权值线段树维护指向该点的某种边的起始点,出现相同类型的括号就进行线段树合并
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<iostream> #include<utility> #include<cstdio> #include<queue> using namespace std; int n,m,K,rt[300005],cnt,lc[12000005],rc[12000005],val[12000005]; long long ans; struct UFS{ int fa[300005],siz[300005]; int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);} void merge(int x,int y){ int fx=find(x),fy=find(y); if(fx!=fy)fa[fx]=fy,siz[fy]+=siz[fx]; } }s,s2; struct Edge{ int u,v,w; }edge[600005]; queue<pair<int,int> >q; inline int read(){ int w=0,f=1; char ch=0; while(ch<'0'||ch>'9'){if(ch=='-') f=-1; ch=getchar();} while(ch>='0'&&ch<='9')w=(w<<1)+(w<<3)+ch-'0',ch=getchar(); return w*f; } void update(int &i,int l,int r,int p,int v){ if(!i)i=++cnt; if(l==r){ if(!val[i])val[i]=v; else if(s2.find(val[i])!=s2.find(v))s2.merge(val[i],v),q.push(make_pair(val[i],v)); return; } int mid=l+r>>1; if(p<=mid)update(lc[i],l,mid,p,v); else update(rc[i],mid+1,r,p,v); } int merge(int l,int r,int u,int v){ if(!u||!v)return u+v; if(l==r){ if(!val[u]||!val[v])val[u]=val[u]+val[v]; else if(s2.find(val[u])!=s2.find(val[v]))s2.merge(val[u],val[v]),q.push(make_pair(val[u],val[v])); return u; } int mid=l+r>>1; lc[u]=merge(l,mid,lc[u],lc[v]),rc[u]=merge(mid+1,r,rc[u],rc[v]); return u; } int main(){ for(int i=1;i<=300000;i++)s.fa[i]=i,s2.fa[i]=i,s.siz[i]=s2.siz[i]=1; n=read(),m=read(),K=read(); for(int i=1;i<=m;i++)edge[i]=(Edge){read(),read(),read()},update(rt[edge[i].v],1,K,edge[i].w,edge[i].u); while(q.size()){ int x=s.find(q.front().first),y=s.find(q.front().second); q.pop(); if(x!=y)s.merge(x,y),rt[y]=merge(1,K,rt[x],rt[y]); } for(int i=1;i<=n;i++)if(s.find(i)==i)ans+=1ll*s.siz[i]*(s.siz[i]-1)/2; printf("%lld\n",ans); return 0; }