0%

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

Problem Description

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

Input

本题目包含多组数据,请处理到文件结束。 每组测试第一行包含两个整数N和M(0<N<=10000,0<M<20000)。其中N表示教室的数目(教室编号从0到N-1),M表示通气管道的数目。 第二行有N个整数Vi(0<=Vi<=1000),分别代表每个教室的人数。 接下来有M行,每行两个整数Ai,Bi(0<=Ai,Bi<N),表示教室Ai和教室Bi之间建了一个通气管道。

Output

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

Sample Input

1
2
3
4
5
6
7
8
9
10
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

1
2
0
1

Key1

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

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

Code1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
#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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
#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;
}