Codeforces Round #686 (Div. 3) E. Number of Simple Paths 拓扑排序+组合数学

题意:给一个有n个点,n条边的联通图。问有多少条简单路径。
简单路径是指:一条边最多经过一次的路径。

思路:首先,这个联通图,可以看成是一棵树,然后再加上一条边。
对于一棵树的话,任意两点都可到达,简单路径就是N * (N-1) / 2
然后考虑加上一条边之后,很容易知道,一棵树,加上一条边,必形成一个环。
然后我们考虑多的这条边会带来的贡献即可。

在这里插入图片描述
如上图所示,把环上每个点作为根,会分成三颗树。对于在同一颗子树中的点,每个点到自己所在子树内任意一点的简单路径只有一条,而到达不同子树的路径则有两条。
所以dfs一次,处理出来以环上的点作为根的子树大小。
1、对于蓝色内部的点,可以多的贡献就是蓝色子树大小乘绿色和红色部分的结点个数
2、对于绿色部分,可以多的贡献就是绿色子树大小乘上红色子树结点大小因为蓝色和绿色之间,在前面蓝色部分已经算过了。

所以上图的答案就是10 * 9 / 2 =45
多的贡献就是5 * 5 + 1 * 4 =29
45 + 29 = 74

环上的结点 我们可以用拓扑排序处理出来

#include<bits/stdc++.h>
using namespace std;
const int N=4e5+50;
typedef long long ll;
vector<int> e[N];
bool vis[N];
int in[N],siz[N];
void dfs(int x,int f){
    
    
    siz[x]=1;
    for(auto y:e[x]){
    
    
        if(y==f || in[y]>1) continue;
        dfs(y,x);
        siz[x]+=siz[y];
    }
}
int main(){
    
    
    int T;cin>>T;
    while(T--){
    
    
        int n;cin>>n;
        for(int i=1;i<=n;i++) siz[i]=in[i]=0,e[i].clear();
        for(int i=1;i<=n;i++){
    
    
            int x,y;cin>>x>>y;
            e[x].push_back(y);
            e[y].push_back(x);
            in[x]++;
            in[y]++;
        }
        queue<int> que;
        for(int i=1;i<=n;i++){
    
    
            if(in[i]==1) que.push(i);
        }
        while(!que.empty()){
    
    
            int x=que.front();
            que.pop();
            for(auto y:e[x]){
    
    
                --in[y];
                if(in[y]==1) que.push(y);
            }
        }
        vector<int>a;
        ll ans=1ll*n*(n-1)/2;
        for(int i=1;i<=n;i++){
    
    
            if(in[i]>1){
    
    
                dfs(i,-1);
                a.push_back(siz[i]);
            }
        }
        for(auto i:a){
    
    
            ans+=1ll*i*(n-i);
            n-=i;
        }
        cout<<ans<<endl;
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_43563669/article/details/110494962