-
Notifications
You must be signed in to change notification settings - Fork 0
/
12668-Attacking-Rooks.cpp
243 lines (216 loc) · 8.77 KB
/
12668-Attacking-Rooks.cpp
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
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> ii;
#define forr(i,a,b) for(int i=(a); i <(b); i++)
#define forn(i,n) forr(i,0,n)
#define sz(v) (int)(v).size()
#define debug(v) cerr << #v << ": " << (v) << endl;
#define forall(it,v) for(auto it = v.begin(); it != v.end(); it++)
#define dforn(i,n) for(int i = (n)-1; i >= 0; i--)
#define zero(v) memset(v, 0, sizeof(v));
#define pb push_back
#define mp make_pair
#define debugc(c) do{cerr << #c << ": "; forall(it, c) {cerr << *it << " ";} cerr << endl; } while(0);
constexpr int noparent = -1;
/**
* Algoritmo de Dinitz (llamado usualmente dinics). Observaciones:
* - los nodos se numeran de 0 a n-1. En caso en que se numeren de 1 a n, simplemente generar un flowgraph con n+1 vertices
* sin conectar nada al vertice 0.
*
* COMPLEJIDAD
* - Este algoritmo tiene complejidad O(n^2m), lo cual es mas que suficiente para competencias (salvo push relabel para grafos densos).
* - En el caso en que todas las capacidades sean 1, cuesta O(min(m^1/2, n^2/3)m) lo cual es O(n^{2/3}m) para caso denso O(m^{3/2}) para ralos .
* - Cuando el grafo es bipartito cuesta O(sqrt(n)m), lo cual matchea con el que se usa en competencia (de Hopcroft y Karp) y es
* mejor que el de Berge.
*
* NOTAS:
* Esta implementacion es basica, separada en subtareas, sin mezclar nada para mostrar una implementacion casi directa.
* (ver dinics-fast.cpp)
*/
class flowgraph {
struct edge {
long long from, //vertice del que parte la arista; solo por simetria y para simplificar, se puede sacar
to, //vertice al que llega la arista
capacity, //capacidad de la arista
flow, //flujo de la arista, el residual es capacity - flow
reverse; //indice a la arista reversa en el grafo residual
};
template<class T>
using digraph = vector<vector<T>>;
digraph<edge> residual;
inline edge& reverse(const edge& e) {
return residual[e.to][e.reverse];
}
/**
* Computa el grafo de niveles usando BFS a partir del residual.
* Retorna un grafo donde por cada vertice se tienen los indices de los vecinos
* que pertenecen al level graph en el grafo residual.
*/
digraph<int> find_level_graph(int source) {
digraph<int> level_graph(residual.size());
vector<int> level(residual.size(), noparent);
deque<int> q(1, source);
level[source] = 0;
while(not q.empty()) {
auto v = q.front(); q.pop_front();
for(int idx = 0; idx < residual[v].size(); ++idx) {
auto& e = residual[v][idx];
if(e.flow == e.capacity) continue;
if(level[e.to] == noparent) {
q.push_back(e.to);
level[e.to] = level[v] + 1;
}
if(level[e.to] > level[v]) level_graph[v].push_back(idx);
}
}
return level_graph;
}
/**
* Aplica DFS para encontrar un augementing path mientras se busca el blocking flow.
* Retorna el valor del augmenting path, que es valido cuando dicho valor es mayor a 0.
* En parent e index deja anotada la arista con la que se llega a cada vertice hasta sink
* Anula los dead-ends del level_graph cuando los encuentra.
*/
long long find_augmenting_path(digraph<int>* level_graph, int from, int sink, vector<int>* parent, vector<int>* index) {
while(not level_graph->at(from).empty()) {
auto& e = residual[from][level_graph->at(from).back()];
auto success = e.capacity-e.flow;
if(success > 0 && e.to != sink) success = min(success, find_augmenting_path(level_graph, e.to, sink, parent, index));
if(success == 0) {
//arista saturada! o dead end!
level_graph->at(from).pop_back();
continue;
}
//camino encontrado. Guardamos la informacion y retornamos el flujo
parent->at(e.to) = e.from;
index->at(e.to) = level_graph->at(from).back();
return min(success, e.capacity - e.flow);
}
//no habia augmenting path
return 0;
}
/**
* Busca iterativamente los augmenting paths, aumentandolos hasta tener un blocking flow. Retorna
* el valor del flujo aumentado.
* Requiere: que ninguna arista este en el level graph, ya que se calcula en esta etapa.
*/
long long find_blocking_flow(int source, int sink) {
auto level_graph = find_level_graph(source);
vector<int> parent(residual.size(), noparent);
vector<int> index(residual.size(), 0);
long long ans=0, volume;
while((volume = find_augmenting_path(&level_graph, source, sink, &parent, &index)) > 0) {
for(int to = sink; parent[to] != noparent; to = parent[to]) {
auto& e = residual[parent[to]][index[to]];
e.flow += volume;
reverse(e).flow -= volume;
}
ans += volume;
}
return ans;
}
public:
flowgraph(int n) {
residual.assign(n, vector<edge>());
}
void add_edge(int from, int to, long long capacity) {
if(capacity <= 0) return;
auto idxfrom = (int)residual[from].size(), idxto = (int)residual[to].size();
residual[from].push_back(edge{from, to, capacity, 0, idxto});
residual[to].push_back(edge{to, from, 0, 0, idxfrom});
}
/**
* Carga en this el flujo maximo de source a sink. Notar que this podria
* tener un flujo precargado y lo modifica para tener el flujo maximo.
* Retorna todo el flujo que se pudo agregar.
*/
long long maxflow(int source, int sink) {
long long res = 0, step;
while((step = find_blocking_flow(source, sink)) > 0) {
res += step;
}
return res;
}
void print(ostream& out) {
for(int f = 0; f < residual.size(); ++f) {
cout << f << ": ";
for(auto e : residual[f]) {
auto& rev = reverse(e);
out << "(" << e.from << "," << e.to << "," << e.capacity << "," << e.flow << ") {"
<< rev.from << "," << rev.to << "," << rev.capacity << "," << rev.flow << "} - ";
}
cout << endl;
}
}
};
int main() {
ios::sync_with_stdio(false); cin.tie(NULL); cout.tie(NULL);
int N;
/*
La idea es hacer un matching bipartito con "subcolumnas" y "subfilas".
Las filas/columnas se parten en "subfilas"/"subcolumnas" en sus tramos separados por los peones X:
Tablero:
X . . . .
X . . . .
. . X . .
. X . . .
. . . . X
Subilfas:
- 1 1 1 1
- 2 2 2 2
3 3 - 4 4
5 - 6 6 6
7 7 7 7 -
Subcolumnas:
- 2 4 6 7
- 2 4 6 7
1 2 - 6 7
1 - 5 6 7
1 3 5 6 -
En el grafo bipartito, cada sufila y cada subcolumna tiene un nodo.
Un nodo subcolumna está conectado con las subfilas que cruza.
Así podemos imaginar que para cada subcolumna queremos elegir un casillero
(una subfila que la cruza) para poner una torre, y debemos hacerlo
de tal forma de no poner dos torres en la misma subfila.
*/
while(cin >> N){
vector<vector<char>> tablero(N, vector<char>(N));
vector<vector<int>> subcols(N, vector<int>(N, -1));
vector<vector<int>> subrows(N, vector<int>(N, -1));
forn(i,N) forn(j,N) cin >> tablero[i][j];
int gsize = 2*N*(N+1)+2; // cota de nodos: N*(N+1) acota subfilas, N*(N+1) acota subcolumnas, + fuente + sumidero
int t = gsize-1;
flowgraph g(gsize);
int node = 1;
forn(i,N) {
forn(j,N) {
if(tablero[i][j] == 'X'){
// ya terminé esta subcolumna
g.add_edge(0, node, 1); // conecto en el grafo la fuente con la columna, peso 1
node++;
}else{
subcols[i][j] = node;
}
}
g.add_edge(0, node, 1);
node++;
}
forn(j,N) {
forn(i,N) {
if(tablero[i][j] == 'X'){
g.add_edge(node, t, 1); // conecto la subfila terminada con el sumidero
node++;
}else{
subrows[i][j] = node;
}
}
g.add_edge(node, t, 1);
node++;
}
forn(i,N) forn(j,N) if(tablero[i][j] != 'X') {
g.add_edge(subcols[i][j], subrows[i][j], 1);
}
cout << g.maxflow(0, t) << endl;
}
}