https://www.luogu.org/problemnew/show/P4306
Problem:给你一张个点
条边的有向图
,现定义点对
表示连通关系:从点
能直接或间接到达点
,问你图中存在多少对这样的关系。
关于原题的题意,需注意几点:
1.因为题目给的是有向图,所以(存在从点
到达点
的路径,下同)并不一定代表
(存在从点
到达点
的路径)。
2.也算是一个合法的点对。
明确这两点后我们就可以考虑怎么做了。
因为涉及点与点之间之间的连通关系,我首先往tarjan上想的(虽然听大佬说他们都是用Floyd传递闭包做的emmm)。将原图缩完点之后,我们可知:对于点,它能到达的点只会是两种:
1.和它同在一个强联通分量的点;
2.点所在的强联通分量所能到达的其它强联通分量里的所有点。
所以,我们以每一个强联通分量为单位统计答案。
设强连通分量内的点的个数为
,那么强连通分量
对该题答案的贡献就是:
。
AC Code:
#include<cstdio>
#include<cstring>
#include<iostream>
#define ri register int
using namespace std;
const int MAXN=2020;
int n,m,u[MAXN*MAXN],v[MAXN*MAXN],fst[MAXN*MAXN],nxt[MAXN*MAXN];
int dfn[MAXN],low[MAXN],sta[MAXN],top,cnt,vis[MAXN],ctot,b[MAXN],stot,from[MAXN*MAXN],to[MAXN*MAXN];
int book[MAXN],w[MAXN],dp[MAXN],ans;
char ss[MAXN];
void tarjan(int x)
{
dfn[x]=++cnt,low[x]=dfn[x],sta[++top]=x,vis[x]=1;
for(ri k=fst[x];k>0;k=nxt[k])
{
if(dfn[v[k]]&&vis[v[k]]) low[x]=min(low[x],dfn[v[k]]);
if(!dfn[v[k]])
{
tarjan(v[k]);
low[x]=min(low[x],low[v[k]]);
}
}
if(low[x]==dfn[x])
{
ctot++;
while(1)
{
vis[sta[top]]=0,b[sta[top]]=ctot,w[ctot]++;
top--;
if(sta[top+1]==x) break;
}
}
}
void dfs(int x,int num)
{
book[x]=1;
for(ri k=fst[x];k>0;k=nxt[k])
if(!book[to[k]])
{
dp[num]+=w[to[k]];
dfs(to[k],num);
}
}
int main()
{
scanf("%d",&n);
for(ri i=1;i<=n;i++)
{
scanf("%s",ss+1);
for(ri j=1;j<=n;j++)
{
if(ss[j]=='1')
{
++m;
u[m]=i,v[m]=j;
nxt[m]=fst[u[m]],fst[u[m]]=m;
}
}
}
for(ri i=1;i<=n;i++)
if(!dfn[i]) tarjan(i);
memset(fst,0,sizeof(fst));
memset(nxt,0,sizeof(nxt));
for(ri i=1;i<=m;i++)
{
if(b[u[i]]==b[v[i]]) continue;
stot++;
from[stot]=b[u[i]],to[stot]=b[v[i]];
nxt[stot]=fst[from[stot]],fst[from[stot]]=stot;
}
n=ctot;
for(ri i=1;i<=n;i++)//O(n^2)算每一个SCC对答案的贡献
{
memset(book,0,sizeof(book));
dfs(i,i);
dp[i]*=w[i];
}
for(ri i=1;i<=ctot;i++)
ans+=w[i]*w[i]+dp[i];
cout<<ans;
return 0;
}