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