#HDU 4635 Strongly connected (tarjan + 原理)

Problem Description

Give a simple directed graph with N nodes and M edges. Please tell me the maximum number of the edges you can add that the graph is still a simple directed graph. Also, after you add these edges, this graph must NOT be strongly connected.
A simple directed graph is a directed graph having no multiple edges or graph loops.
A strongly connected digraph is a directed graph in which it is possible to reach any node starting from any other node by traversing edges in the direction(s) in which they point.

 

 

Input

The first line of date is an integer T, which is the number of the text cases.
Then T cases follow, each case starts of two numbers N and M, 1<=N<=100000, 1<=M<=100000, representing the number of nodes and the number of edges, then M lines follow. Each line contains two integers x and y, means that there is a edge from x to y.

 

 

Output

For each case, you should output the maximum number of the edges you can add.
If the original graph is strongly connected, just output -1.

 

 

Sample Input

 

3 3 3 1 2 2 3 3 1 3 3 1 2 2 3 1 3 6 6 1 2 2 3 3 1 4 5 5 6 6 4

 

 

Sample Output

 

Case 1: -1 Case 2: 1 Case 3: 15

题目大意 : 输入一个有向图, 没有环,没有重边,问最多添加多少条边使他刚好不是强连通图

这道题其实是需要推规律的,我先自己推了一遍感觉没问题,然后看了别人的题解,发现思路基本一致才开始写的,说一下我的思考过程。

思路 : 强连通图指的就是任意两点之间都直接或间接地有连线,或者说缩点后只有一个点。题意中我说到刚好不是强连通,意思就是如果再接一条线,不管是哪一条,他都会变成强连通图,所以这个临界点就是关键点。这道题最有意思的是他的那个最多,有一种题目是问你最少添加几条边使他称为强连通图, 这种题我们是求缩点后入度出度为0的个数的最大值, 而这个,也是建立在入度出度为0的基础之上的,画一张图

 

这两张图反应的就是一个强连通图的最大状态,也就是一张完全图,任意两边之间都有连线, 一定是强连通图,那么强连通图的最小状态是什么呢?不难想到就是一个环了,环和完全图可以给我们提供的信息就是,在一张图差一条线即可成环时,其他可以添加的但不构成环的边全部添加上,就是所求的最大添加边数。再给张图感受下

这个表示的是一整张图,并且也涵盖了三种情况,对比下缩点后的图

思考下这句话,如果想要可连的边更多, 应该让被连的那个点包含的点集更小,这张图里, 2、3、4都只包含了一个点,我们先以4为基点,如果4没有到其他点的边而其他的点都有到4的边;再以1为基点,1中所有的点没有到其他点的边而其他边有到1的边。这两种情况,不难看出,第一种情况可以添加的边数一定大于第二种情况,因为1的点集包含的点多啊QAQ你多你还不连其他人让其他人来连你,是不是很不礼貌?所以我们就需要一个记录点集里点数目的数组,那什么样的点可以作为基点呢?想一下就应该知道是入度或出度为0的点,到这,问题基本已经解决了,我们再顺一下。

先求出完全图的点数,也就是 N * (N - 1),再减去已经有的点数 = N * (N - 1) - M,再找到入度或出度为0的最小点集,让其他每个点都连上他(因为开头已全部经把边全部减掉了,所以不用担心重复) = (N - 最小点集数) * 最小点集数,综合一下就是,N * (N - 1) - M - min * (N - min);

形象地概括 :为了让边更多,入度 or 出度为0的最小点集的集合只好牺牲自己,你们互相连吧,别管我了QAQ

AC代码 :

#include<iostream>
#include<cstring>
#include<stack>
using namespace std;
const int maxn = 1e5 + 5;
const int INF = 0x3f3f3f3f;

struct node
{
    int v, next;
}e[maxn];
int head[maxn], suo[maxn], dfn[maxn], low[maxn];
int sum[maxn], in[maxn], out[maxn];
int n, m, cnt, tot, scnt, T;
bool vis[maxn];
stack <int> st;
void init() {
    memset(e, 0, sizeof(e));
    memset(head, -1, sizeof(head));
    memset(suo, 0, sizeof(suo));
    memset(dfn, 0, sizeof(dfn));
    memset(low, 0, sizeof(low));
    memset(sum, 0, sizeof(sum));
    memset(vis, 0, sizeof(vis));
    memset(out, 0, sizeof(out));
    memset(in, 0, sizeof(in));
    cnt = tot = scnt = 0;
}
void add (int u, int v) {
    e[++cnt].v = v;
    e[cnt].next = head[u];
    head[u] = cnt;
}
void tarjan(int x) {
    dfn[x] = low[x] = ++tot;
    vis[x] = 1;
    st.push(x);
    for (int i = head[x]; i != -1; i = e[i].next) {
        int v = e[i].v;
        if (!dfn[v]) {
            tarjan(v);
            low[x] = min (low[x], low[v]);
        }
        else if (vis[v]) low[x] = min (low[x], dfn[v]);
    }
    if (dfn[x] == low[x]) {
        scnt++;
        int k;
        do {
            k = st.top();
            st.pop();
            vis[k] = 0;
            suo[k] = scnt;
            sum[scnt]++;
        }
        while (k != x);
    }
}

int main()
{
    cin >> T;
    for (int j = 1; j <= T; j++) {
        cin >> n >> m;
        init();
        for (int i = 0; i < m; i++) {
            int ui, vi;
            cin >> ui >> vi;
            add (ui, vi);
        }
        for (int i = 1; i <= n; i++) {
            if (!dfn[i]) tarjan(i);
        }
        cout << "Case " << j << ": ";
        if (scnt == 1) {cout << -1 << endl; continue;}  //强连通输出-1
        for (int i = 1; i <= n; i++) {
            for (int j = head[i]; j != -1; j = e[j].next) {
                int u = suo[i], v = suo[e[j].v];
                if (u != v) in[v]++, out[u]++;
            }
        }
        int ans = n * (n - 1) - m, min_ = INF;
        for (int i = 1; i <= scnt; i++) {
            if (!in[i] || !out[i]) min_ = min (min_, sum[i]);
        }
        cout << ans - min_ * (n - min_) << endl;
    }
    return 0;
}

 

 

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值