Codeforces 1454E - Number of Simple Paths (dfs)
Codeforces Round #686 (Div. 3) E. Number of Simple Paths
题意
给定一张\(n\)个点\(n\)条边的图,问图中简单路径的数量。
限制
\(1\le t\le 2\times 10^4\)
\(3\le n\le 2\times 10^5\)
\(\sum n\le 2\times 10^5\)
思路
\(n\)个点\(n\)条边,顾名思义就是这张图中只存在一个环(基环图)
如果将这个环中任意去掉一条边,剩下的图将会是棵树
对于这个环,可以直接dfs一遍求出
考虑起点和终点都在环内的简单路径,假设环内顶点数为\(m\)
明显,每两个点之间都严格存在两条简单路径
故简单路径数为\(2C_m^2=m*(m-1)\)
接下来遍历基环内的点,通过这些点遍历伸展出去的树
假设当前遍历到的基环内的点为\(u\)
假设此时遍历的树的节点数为\(cnt\)(不包括\(u\))
假设已经遍历过的\(u\)的其它子树中节点数总和为\(nowNodeCount\)
假设除了\(u\)及\(u\)的子树,其他已经遍历过的节点数总和为\(totalSiz\)
一、考虑起点和终点都在遍历的树中(包括\(u\))的简单路径
明显这是棵树,做法与在环内相同,但每两个点之间仅存在一条路径
故答案为\(C_{cnt+1}^2=\frac {cnt*(cnt+1)} 2\)
二、考虑起点在遍历的树中(包括\(u\)),终点在环内(不包括\(u\))以及其他已经遍历过的节点(不是\(u\)的子树内节点)的简单路径
明显,起点到终点会经过环,故总是存在两条路径
故答案为\(2*cnt*totalSiz\)
三、考虑起点在遍历的树中(包括\(u\)),终点在\(u\)的其他子树内的已经遍历过的节点的简单路径
起点到终点有且仅有一次经过节点\(u\),故只存在一条路径
故答案为\(cnt*nowNodeCnt\)
程序
(171ms/2000ms)
//#pragma GCC optimize(3)
//#include<ext/pb_ds/assoc_container.hpp>
//#include<ext/pb_ds/hash_policy.hpp>
#include<bits/stdc++.h>
#define closeSync ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)
#define multiCase int T;cin>>T;for(int t=1;t<=T;t++)
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define repp(i,a,b) for(int i=(a);i<(b);i++)
#define per(i,a,b) for(int i=(a);i>=(b);i--)
#define perr(i,a,b) for(int i=(a);i>(b);i--)
#define pb push_back
#define eb emplace_back
#define mst(a,b) memset(a,b,sizeof(a))
using namespace std;
//using namespace __gnu_pbds;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> P;
const int INF=0x3f3f3f3f;
const ll LINF=0x3f3f3f3f3f3f3f3f;
const double eps=1e-12;
const double PI=acos(-1.0);
const double angcst=PI/180.0;
const ll mod=998244353;
ll gcd(ll a,ll b){return b==0?a:gcd(b,a%b);}
ll qmul(ll a,ll b){ll r=0;while(b){if(b&1)r=(r+a)%mod;b>>=1;a=(a+a)%mod;}return r;}
ll qpow(ll a,ll n){ll r=1;while(n){if(n&1)r=(r*a)%mod;n>>=1;a=(a*a)%mod;}return r;}
ll qpow(ll a,ll n,ll p){ll r=1;while(n){if(n&1)r=(r*a)%p;n>>=1;a=(a*a)%p;}return r;}
vector<int> G[200050],circle;
bool vis[200050],incircle[200050];
ll cnt;
bool dfs(int p,int fa)
{
if(vis[p]) //如果已经访问过,说明找到了环
{
circle.pb(p);
return true;
}
vis[p]=true;
for(int it:G[p])
{
if(it==fa)
continue;
if(dfs(it,p))
{
circle.pb(p);
return true; //进行递归
}
}
return false;
}
void dfs2(int p,int fa)
{
cnt++; //计数
for(int it:G[p])
{
if(it==fa)
continue;
dfs2(it,p);
}
}
void solve()
{
int n;
cin>>n;
rep(i,1,n)
{
G[i].clear();
vis[i]=false;
incircle[i]=false;
}
circle.clear();
rep(i,1,n)
{
int a,b;
cin>>a>>b;
G[a].pb(b);
G[b].pb(a);
}
dfs(1,0); //寻找环内节点
repp(i,1,circle.size())
if(circle[i]==circle[0])
{
circle.erase(circle.begin()+i,circle.end()); //删除多余节点,使得circle内仅存在一遍环内节点
break;
}
for(int it:circle)
incircle[it]=true; //标记
ll siz=circle.size();
ll ans=siz*(siz-1); //仅看环内节点的答案
ll totalSiz=siz-1;
for(int it:circle)
{
ll nowNodeCount=0;
for(int it2:G[it])
{
if(incircle[it2])
continue;
cnt=0;
dfs2(it2,it); //寻找it节点it2方向的子树节点数量
ans+=cnt*(cnt+1)/2+cnt*(totalSiz*2+nowNodeCount); //加入答案
nowNodeCount+=cnt; //作为it节点其它子树节点数量
}
totalSiz+=nowNodeCount; //作为环内其他节点已经遍历过的节点数量
}
cout<<ans<<'\n';
}
int main()
{
closeSync;
multiCase
{
solve();
}
return 0;
}