牛客国庆day8 BZOJ 4450. [Neerc2015]Cactus Jubilee(tarjan,求方案数)

链接:https://ac.nowcoder.com/acm/contest/7865/B
来源:牛客网

题目描述
This is the 20-th Northeastern European Regional Contest (NEERC). Cactus problems had become a NEERC tradition. The first Cactus problem was given in 2005, so there is a jubilee — 10 years of Cactus.
Cactus is a connected undirected graph in which every edge lies on at most one simple cycle. Intuitively cactus is a generalization of a tree where some cycles are allowed. Multiedges (multiple edges between a pair of vertices) and loops (edges that connect a vertex to itself) are not allowed in a cactus.
You are given a cactus. Let’s move an edge — remove one of graph’s edges, but connect a different pair of vertices with an edge, so that a graph still remains a cactus. How many ways are there to perform such a move?

For example, in the left cactus above (given in the first sample), there are 42 ways to perform an edge move. In the right cactus (given in the second sample), which is the classical NEERC cactus since the original problem in 2005, there are 216 ways to perform a move.

输入描述:
The first line of the input file contains two integers n and m (1 ≤ n ≤ 50 000, 0 ≤ m ≤ 50 000). Here n is the number of vertices in the graph. Vertices are numbered from 1 to n. Edges of the graph are represented by a set of edge-distinct paths, where m is the number of such paths.
Each of the following m lines contains a path in the graph. A path starts with an integer ki (2 ≤ ki ≤ 1000) followed by ki integers from 1 to n. These ki integers represent vertices of a path. Adjacent vertices in a path are distinct. Path can go to the same vertex multiple times, but every edge is traversed exactly once in the whole input file.
The graph in the input file is a cactus.
输出描述:
Write to the output file a single integer — the number of ways to move an edge in the cactus.
示例1
输入
复制
6 1
7 1 2 5 6 2 3 4
输出
复制
42
示例2
输入
复制
15 3
9 1 2 3 4 5 6 7 8 3
7 2 9 10 11 12 13 10
5 2 14 9 15 10
输出
复制
216

题意:
一个仙人掌图(连通且每个边至多在一个环上)。
给你一个仙人掌图,求删掉一条边并且将这条边补在另外一个地方,这个图仍是仙人掌图。求方案数。

思路:
根据仙人掌图的特点可知,如果将环当成一个点,那么这个图实际构成了一棵树。
所以所有的边可以分为两种:

  1. 环边
  2. 树边,也就是割边

如果删除的边是树边,那么方案数就是分成的两个连通块的乘积减一。这部分答案在tarjan求割边的时候可以直接求出来。

如果删除的边是环边,则要事先维护所有的树边连通块,答案先加上所有连通块内的组合方案,也就是 x ∗ ( x − 1 ) / 2 − ( x − 1 ) x*(x-1)/2-(x-1) x(x1)/2(x1)。连通块可以用并查集维护。

然后枚举删除的环上每个环上点所连接的树边连通块数目,减去这些连通块的组合方案。之后这个删掉一条边的环和环所连树边组成一个大的树边连通块,再加上这个新连通块的方案数,乘以环上边的数目(删掉这条边每个环都是这么多方案)。

#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
#include<map>

using namespace std;

typedef long long ll;
const int maxn = 1e5 + 7;

int n,m;
int fa[maxn],cnt[maxn],pre[maxn];
int siz[maxn];
int dfn[maxn],low[maxn],num;
map<pair<int,int>,int>mp; //存割边
vector<int>G[maxn];
ll ans,sum;

int findset(int x) {
    
    
    if(x == fa[x]) return x;
    return fa[x] = findset(fa[x]);
}

void tarjan(int x,int fa) {
    
    
    siz[x] = 1;
    dfn[x] = low[x] = ++num;
    for(int i = 0;i < G[x].size();i++) {
    
    
        int v = G[x][i];
        if(v == fa) continue;
        if(!dfn[v]) {
    
    
            tarjan(v,x);
            siz[x] += siz[v];
            low[x] = min(low[x],low[v]);
            if(low[v] > dfn[x]) {
    
     //割边
                mp[{
    
    x,v}] = mp[{
    
    v,x}] = 1;
                ans += 1ll * siz[v] * (n - siz[v]) - 1;
            }
        } else {
    
    
            low[x] = min(low[x],dfn[v]);
        }
    }
}

void dfs1(int x,int f) {
    
    
    dfn[x] = 1;
    for(int i = 0;i < G[x].size();i++) {
    
    
        int v = G[x][i];
        if(v == f) continue;
        if(!dfn[v] && mp.find({
    
    x,v}) != mp.end()) {
    
    
            dfs1(v,x);
            int rx = findset(x),ry = findset(v);
            if(rx != ry) {
    
    
                fa[rx] = ry;
                cnt[ry] += cnt[rx];
            }
        }
    }
}

void dfs2(int x,int f) {
    
    
    dfn[x] = 1; //寻找返祖边
    for(int i = 0;i < G[x].size();i++) {
    
    
        int v = G[x][i];
        if(v == f) continue;
        if(!dfn[v]) {
    
    
            pre[v] = x;
            dfs2(v,x);
        } else if(dfn[v] == 1) {
    
     //返祖边
            ll res = sum;
            ll c1 = 0; //环所连的树边
            ll c2 = 0; //环上的边数目
            for(int now = x;;now = pre[now]) {
    
    
                int fx = findset(now);
                c1 += cnt[fx];
                c2++;
                res -= 1ll * cnt[fx] * (cnt[fx] - 1) / 2 - (cnt[fx] - 1);
                if(now == v) break;
            }
            res = (res + 1ll * c1 * (c1 - 1) / 2 - (c1 - 1) - 1) * c2;
            ans += res;
        }
    }
    dfn[x] = 2;
}

int main() {
    
    
    scanf("%d%d",&n,&m);
    for(int i = 1;i <= n;i++) {
    
    
        fa[i] = i;
        cnt[i] = 1;
    }
    for(int i = 1;i <= m;i++) {
    
    
        int pre = 0;
        int k;scanf("%d",&k);
        for(int j = 1;j <= k;j++) {
    
    
            int x;scanf("%d",&x);
            if(pre) {
    
    
                G[x].push_back(pre);
                G[pre].push_back(x);
            }
            pre = x;
        }
    }
    
    tarjan(1,-1);
    memset(dfn,0,sizeof(dfn));

    for(int i = 1;i <= n;i++) {
    
    
        if(!dfn[i]) dfs1(i,-1);
    }


    for(int i = 1;i <= n;i++) {
    
    
        if(i == fa[i]) {
    
    
            sum += 1ll * (cnt[i] - 1) * cnt[i] / 2 - (cnt[i] - 1);
        }
    }

    memset(dfn,0,sizeof(dfn));
    dfs2(1,-1);
    
    printf("%lld\n",ans);
    
    return 0;
}

猜你喜欢

转载自blog.csdn.net/tomjobs/article/details/109036835