题目描述
小猫在研究有向图。小猫在研究联通性。
给定一张N个点,M条边的有向图,问有多少点对(u,v)(u<v),满足u能到达v且v也能到达u。
输入描述
第一行两个正整数N,M,表示点数与边数。接下来M行,第i行两个正整数ui,vi,表示一条从ui到vi的边,保证ui≠vi。
输出描述
一行一个整数,表示点对数量。
示例
输入:
3 3
1 2
2 3
3 2
输出:
1
思路:
(1)Kosaraju:
1.对原图进行dfs,后序记录顶点
2.每次选择栈顶的元素出栈,对反图进行dfs,标记能够遍历的顶点,这些顶点构成一个强连通分量
3.如果还有没有标记的元素,继续进行step2,否则算法结束
4.在反向遍历时可以求出强连通分量的个数和每个强连通分量里的数字有哪些
(2)求出每个强连通分量的个数k,根据C(k,2)可求出此连通分量可达点个数
(3)求和:每个强连通分量的C(k,2)相加即整个图的可达点对数量
第一种:
num(当时是第几个强连通分量)充当每个连通分量的老大,set[maxn]数组记录每个强连通分量有哪些点,之后根据每个强连通分量的C(k,2)求和得到结果
#include<iostream>
#include<string.h>
#include<vector>
using namespace std;
const int maxn=1e6+5;
int n,m;
struct node{
int to;
};
vector<node> map[maxn];
vector<node> rmap[maxn];
bool vis1[maxn];
bool vis2[maxn];
int stack[maxn],idex;
int num,set[maxn];
void dfs1(int s){
for(int i=0;i<map[s].size();i++){
int v=map[s][i].to;
if(!vis1[v]){
vis1[v]=1;
dfs1(v);
}
}
stack[++idex]=s;
}
void dfs2(int s,int num){
set[s]=num;
int tmp;
for(int i=0;i<rmap[s].size();i++){
tmp=rmap[s][i].to;
if(!vis2[tmp]){
vis2[tmp]=1;
dfs2(tmp,num);
}
}
}
int main(){
cin>>n>>m;
memset(vis1,0,sizeof(vis1));
memset(vis2,0,sizeof(vis2));
int u,v;
node tmp;
idex=0;
num=0;
while(m--){
cin>>u>>v;
tmp.to=v;
map[u].push_back(tmp);
tmp.to=u;
rmap[v].push_back(tmp);
}
for(int i=1;i<=n;i++){//深搜原图
if(!vis1[i]){
vis1[i]=1;
dfs1(i);
}
}
while(idex){
int tmp=stack[idex--];
if(!vis2[tmp]){
vis2[tmp]=1;
dfs2(tmp,++num);
}
}
//假设某个强连通分量k个点,那么u<==>v(u,v相互可达)边数为C(k,2)
//先算出一个图有几个强连通分量,然后计算每个强连通分量有几个点,根据C(k,2)计算之后相加
int ret,sum=0;
for(int i=1;i<=num;i++){
ret=0;
for(int j=1;j<=n;j++){
if(set[j]==i){
ret++;
}
}
// cout<<"ret:"<<ret<<endl;
sum+=ret*(ret-1)/2;
}
printf("%d\n",sum);
return 0;
}
第二种:
利用缩点f[v]=y,y遍历点的起点,也是它所在强连通分量的老大,在1-n个点中,有多少个不同的f[v]即多少个强连通图,ans[f[v]]数组有几个不为0,即几个强连通图,同时,不为0的值即此连通分量内包含点的个数,所以ans[i]*(ans[i]-1)/2算出一个强连通分量的点对,最后求和
#include <iostream>//(邻接点+vector) Kosaraju
#include <vector>
#include <string.h>
#define _ ios_base::sync_with_stdio(0),cin.tie(0)
using namespace std;
struct Edge{
int to;
}edge;
const int maxn=305;
vector<Edge> mp[maxn];//原图
vector<Edge> remp[maxn];//反图
int n,m;//n个顶点 m条边
bool vis[maxn]; //原图未访问为0,反图未访问为1
int f[maxn];//缩点 (主要作用:滤环,确定几个强连通图)
int p[maxn];//栈 (存放dfs原图后续遍历结果)
int cnt=0;//栈的下标
void dfs(int x){ //dfs原图
for(int i=0;i<mp[x].size();i++){
edge=mp[x][i];
int v=edge.to;
if(!vis[v]){
vis[v]=true;
dfs(v);
}
}
p[cnt++]=x;//后序入栈
}
void redfs(int x,int y){//dfs反图
f[y]=y;//***缩点时,记得要把自己也归进去
for(int i=0;i<remp[x].size();i++){
edge=remp[x][i];
int v=edge.to;
if(vis[v]){
vis[v]=false;
f[v]=y; //缩点 (此处y相当于这个连通图的老大)
redfs(v,y);
}
}
}
void kosaraju(){
for(int i=1;i<=n;i++){
if(!vis[i]){
vis[i]=1;//未访问的点,访问过后置1
dfs(i);
}
}
// for(int i=0;i<cnt;i++)
// cout<<p[i]<<" ";
// cout<<endl;
for(int j=cnt-1;j>=0;j--){
if(vis[p[j]]){
vis[p[j]]=0;
redfs(p[j],p[j]);
}
}
}
int main(){
cin>>n>>m;
memset(vis,false,sizeof(vis));
// memset(f,-1,sizeof(f));
// memset(p,-1,sizeof(p));
cnt=0;
while(m--){
int u,v;
cin>>u>>v;
edge.to=v;
mp[u].push_back(edge);//原图
edge.to=u;
remp[v].push_back(edge);//反图
}
kosaraju();
vector<int>ans(n+1);
for(int i=1;i<=n;i++){
ans[f[i]]++;// ans数组的值不为0的有几个,则强连通分量有几个
}
int sum=0;
for(int i=1;i<=n;i++){
sum+=(ans[i]*(ans[i]-1))/2;//计算每个强连通分量的 C(k,2) 然后相加即相互可达边的个数
// cout<<ans[i]<<endl;
}
cout<<sum;
return 0;
}