题解
这道题有两个非常重要的性质需要发现 其实挺显然的,不过好像大多人发现了没反应过来?:
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;
}