[刷题]HDU2242 - 考研路茫茫——空调教室 (双连通分量)

Problem Description

众所周知,HDU的考研教室是没有空调的,于是就苦了不少不去图书馆的考研仔们。Lele也是其中一个。而某教室旁边又摆着两个未装上的空调,更是引起人们无限YY。
一个炎热的下午,Lele照例在教室睡觉的时候,竟然做起了空调教室的美梦。
Lele梦到学校某天终于大发慈悲给某个教室安上了一个空调。而且建造了了M条通气管道,让整个教学楼的全部教室都直接或间接和空调教室连通上,构成了教室群,于是,全部教室都能吹到空调了。
不仅仅这样,学校发现教室人数越来越多,单单一个空调已经不能满足大家的需求。于是,学校决定封闭掉一条通气管道,把全部教室分成两个连通的教室群,再在那个没有空调的教室群里添置一个空调。
当然,为了让效果更好,学校想让这两个教室群里的学生人数尽量平衡。于是学校找到了你,问你封闭哪条通气管道,使得两个教室群的人数尽量平衡,并且输出人数差值的绝对值。

Input

本题目包含多组数据,请处理到文件结束。
每组测试第一行包含两个整数N和M(0

Output

对于每组数据,请在一行里面输出所求的差值。
如果不管封闭哪条管道都不能把教室分成两个教室群,就输出”impossible”。

Sample Input

4 3
1 1 1 1
0 1
1 2
2 3
4 3
1 2 3 5
0 1
1 2
2 3

Sample Output

0
1

Key1

边的双连通分量。首先是求分量,然后缩点。然后简单的树形DP即可。

要点是,它的图可以有重边。被坑了好久。

Code1

#include<algorithm>
#include<iostream>
#include<cstring>
#include<vector>
#include<cmath>
#include<set>
const int Maxn = 10000 + 23;
using namespace std;

class CutEdge {
private:
    int N;
    int clk, pre[Maxn];

    int DFS(int u, int f) {
        int lowu = pre[u] = ++clk;
        for (auto e = G[u].begin(); e != G[u].end(); ++e) {
            int v = *e;
            if (!pre[v]) {
                int lowv = DFS(v, u);
                lowu = min(lowu, lowv);
                if (lowv > pre[u]) {
                    Cut[u].insert(v);
                    Cut[v].insert(u);
                }
            }
            else if (pre[u] > pre[v]) {
                int cnt = 0; //重复边的处理
                for (const auto &e : G[u]) if (e == v) ++cnt;
                if (cnt > 1 || v != f) {
                    lowu = min(lowu, pre[v]);
                }
            }
        }
        return lowu;
    }

    //求边双联通部分
    void DFS2(int u, int id) {
        ID[u] = id;
        for (const auto &v : G[u]) if (!ID[v]) {
            if (Cut[u].count(v)) {
                ++Num;
                G2[id].push_back(Num);
                G2[Num].push_back(id);
                DFS2(v, Num);
            }
            else DFS2(v, id);
        }
    }

public:
    vector<int> G[Maxn];
    set<int> Cut[Maxn];

    //求边双联通部分
    vector<int> G2[Maxn]; //缩点后的图(以ID为结点)
    int ID[Maxn]; //每个点所在的子图
    int Num; //ID个数

    void Clear(int n) {
        N = n;
        memset(ID, 0, sizeof(ID));
        memset(pre, 0, sizeof(pre));
        for (int i = 1; i <= N; ++i) {
            G[i].clear();
            G2[i].clear();
            Cut[i].clear();
        }
        clk = 0;
        Num = 1;
    }

    void AddEdge(int u, int v) {
        G[u].push_back(v);
        G[v].push_back(u);
    }

    void Find() {
        for (int i = 1; i <= N; ++i)
            if (!pre[i])
                DFS(i, -1);
    }

    //求边双联通部分
    int BCC() {
        DFS2(1, Num);
        return Num;
    }
};


int N, M;
int V[Maxn];
int V2[Maxn];
int DP[Maxn];
bool vis[Maxn];
CutEdge C;

int DFS(int u) {
    vis[u] = true;
    DP[u] = V2[u];
    for (const auto &v : C.G2[u]) {
        if (!vis[v]) DP[u] += DFS(v);
    }
    return DP[u];
}


int main() {
    ios::sync_with_stdio(false);
    while (cin >> N >> M) {
        memset(V2, 0, sizeof(V2));
        memset(vis, 0, sizeof(vis));
        memset(DP, 0, sizeof(DP));
        C.Clear(N);
        for (int i = 1; i <= N; ++i) cin >> V[i];
        int u, v;
        for (int i = 1; i <= M; ++i) {
            cin >> u >> v;
            C.AddEdge(++u, ++v);
        }
        C.Find();
        C.BCC();
        if (C.Num == 1) {
            cout << "impossible\n";
            continue;
        }
        int sum = 0;
        for (int i = 1; i <= N; ++i) {
            sum += V[i];
            V2[C.ID[i]] += V[i];
        }
        DFS(1);
        int res = sum;
        for (int i = 1; i <= C.Num; ++i) {
            res = min(abs(sum - 2 * DP[i]), res);
        }
        cout << res << '\n';
    }
    return 0;
}

Key2

可以的优化是,其实在第一个DFS的时候,由于知道了桥的位置,可以在DFS的时候直接求出v以及v的子节点的和。那么在找到桥{u, v}的时候就直接顺便DP了。

Code2

#include<algorithm>
#include<iostream>
#include<cstring>
#include<vector>
#include<cmath>
#include<set>
const int Maxn = 10000+23;
using namespace std;

int N, M;
int V[Maxn];
int res,sum;

class BCC {
private:
    int N;
    int clk, pre[Maxn];

    int DFS(int u, int f) {
        int &lowu = ID[u];
        lowu= pre[u] = ++clk;
        for (auto e = G[u].begin(); e != G[u].end(); ++e) {
            int v = *e;
            if (!pre[v]) {
                int lowv = DFS(v, u);
                lowu = min(lowu, lowv);
                if (lowv > pre[u]) {
                    ++Num;
                    res = min(abs(sum - 2 * V[v]), res);
                }
            }
            else if (pre[u] > pre[v]) {
                int cnt = 0; //重复边的处理
                for (const auto &e : G[u]) if (e == v) ++cnt;
                if (cnt > 1 || v != f) {
                    lowu = min(lowu, pre[v]);
                }
            }
            /*else if (pre[u]>pre[v] && v != f)
                lowu = min(lowu, pre[v]);*/
        }

        if(f!=-1) V[f] += V[u];

        return lowu;
    }

public:
    vector<int> G[Maxn];
    int ID[Maxn];
    int Num;

    void Clear(int n) {
        N = n;
        memset(ID, 0, sizeof(ID));
        memset(pre, 0, sizeof(pre));
        for (int i = 1; i <= N; ++i) {
            G[i].clear();
        }
        clk = 0;
        Num = 1;
    }

    void AddEdge(int u, int v) {
        G[u].push_back(v);
        G[v].push_back(u);
    }

    int Find() {
        for (int i = 1; i <= N; ++i)
            if (!pre[i])
                DFS(i, -1);
        return Num;
    }
};


BCC C;

int main() {
    ios::sync_with_stdio(false);
    while (cin >> N >> M) {
        C.Clear(N);
        sum = 0;
        for (int i = 1; i <= N; ++i) {
            cin >> V[i];
            sum += V[i];
        }
        int u, v;
        for (int i = 1; i <= M; ++i) {
            cin >> u >> v;
            C.AddEdge(++u, ++v);
        }
        res = sum;
        if (C.Find() == 1) {
            cout << "impossible\n";
        }
        else cout << res << '\n';
    }
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值