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;
}

posted @ 2020-11-25 12:32  StelaYuri  阅读(152)  评论(0编辑  收藏  举报