我正在解决IOI 2007 - FLOOD
中提出的问题,问题也在SPOJ
上。
http://www.spoj.com/OI/problems/FLOOD/
http://www.iarcs.org.in/inoi/online-study-material/problems/flood.php
我为解决方案想了很多。后来我才知道 -
Step 0: Converting a region into a node and
Step 1: Connecting two nodes with edges only if corresponding regions share at least a segment.
Step 2: And then running a BFS on this will simplify the problem.
我仍然无法考虑端到端解决方案,但后来我想让我们试试第0步& 1。 我也失败了。如果前面有图像,我可以很容易地看到区域,但不能以编程方式查看。
The input would be end co-ordinate points of segments.
答案 0 :(得分:0)
当然有很多不同的方法可以解决这个问题。一种可能的方法是使用 Computational Geometry 领域的技术。以下两个概念尤其有用:
Doubly-connected edge list (DCEL):这种数据结构在处理平面图时常用。基本思想是扩展每条边以保存对边旁边区域(也称为“面”)的引用。
实现这一点的一种方法是将每个无向边分成两个有向边,每个方向一个。然后可以扩展每个有向边以保存对其右侧区域的一个引用(从有向边的起始顶点的角度来看)。此外,扩展每条边以存储对反向边(有时称为“对等”或“孪生”边)的引用也很有帮助。
Sweep lining:这类算法在处理平面结构时被广泛使用。从概念上讲,它通过在平面上移动“扫描线”来工作,同时保持一些中间结果。每次扫描线击中平面上的对象时,中间结果都会根据该对象进行更新。当最终对象被扫过后,最终结果应该可以从中间结果推导出来。
在更实际的术语中,这通常是通过按坐标对输入点进行排序来实现的(即首先按 y 坐标,如果两个点具有相同的 y 坐标,则按其 x 坐标)。然后通过对排序点进行迭代来完成“扫描”。
为了给您更多参考,我从 Voronoi diagrams 的构造中熟悉了这些技术。特别是,如果您研究 Fortune's algorithm,您会发现它与我在此处描述的方法有一些相似之处(即它也使用 DCEL 和扫描线)。此外,我很喜欢阅读这篇关于计算几何的 book,它更详细地描述了这些技术。
我将在下面的部分中描述如何将这些技术应用于 FLOOD 问题。完整代码在答案末尾。
为了避免处理指针,我将主要使用索引来表示对象之间的引用。我使用以下别名来说明给定的索引是否应该是顶点、边或面的索引:
using v_index_t = uint32_t;
using e_index_t = uint32_t;
using f_index_t = uint32_t;
constexpr e_index_t NULL_EDGE = 0;
constexpr f_index_t OUTER_PLANE = 0;
每个顶点存储它的位置以及哪些边与顶点相关。
由于输入图的边是轴对齐的,因此每个顶点最多可以有四个边。出于这个原因,对于四个方向中的每一个,我都使用了一个具有四个元素的固定大小数组。如果顶点在某个方向上没有边,则使用特殊索引 NULL_EDGE
。
struct Vertex {
int x;
int y;
std::array<e_index_t, 4> incident;
};
边缘按照 DCEL 方法存储。每条边都是有向的,并持有对其右侧面的引用和对其对等边的引用:
struct Edge {
v_index_t a;
v_index_t b;
e_index_t peer;
f_index_t faceRight;
};
每个面都包含包围该面的边列表:
struct Face {
std::vector<e_index_t> edges;
};
完整图包含顶点、边和面的列表:
struct Graph {
std::vector<Vertex> vertices;
std::vector<Edge> edges;
std::vector<Face> faces;
};
一旦你构建了输入图,构建人脸的基本方法就是:
按坐标对顶点进行排序。
初始化一个 union-find data structure,它最初将每个顶点分配给它自己的分区。分区用于指示哪些顶点可以相互到达。
迭代已排序的顶点。对于每个顶点,检查从该顶点开始的边的方向。对于每条向左或向下的边,检查当前顶点和该边的端点是否在同一个分区中(使用联合查找数据结构的查找操作)。
如果是,边关闭一个内部循环(因为顶点在同一分区中,如果它们可以相互到达)。创造一张新面孔。从当前边开始,通过始终采用最右侧的后继边来跟随包围面的边。对于每个访问过的边,将右侧面的 DCEL 参考设置为指向新创建的面。
如果不是,则在 union-find 数据结构中加入两个分区,以表明这两个顶点可以相互到达(从而可以从其中一个顶点到达已知的每个顶点也可以从另一个)。
假设您已经实现了一个用于顶点排序的辅助函数 getSortedVertices
、一个实现联合查找数据结构的辅助类 VertexPartition
和一个沿边移动的辅助函数 applyFaceRightwards
通过始终取最右边的后继边来包围人脸,识别人脸的主要程序如下所示:
void Graph::createFaceGraph() {
// Sort vertices by coordinate
auto indices{getSortedVertices()};
// Initialize union-find data structure with one partition for each vertex.
VertexPartition partition(vertices.size());
// Sweep over the sorted vertices
for (v_index_t current : indices) {
// Check leftwards and downwards edges. The rightwards and upwards edges
// are handled from the vertex at the other side.
for (Direction direction : {Direction::Left, Direction::Down}) {
e_index_t edgeIndex{vertices[current].incident[direction]};
if (edgeIndex == NULL_EDGE) {
continue;
}
Edge &edge{edges[edgeIndex]};
v_index_t other = edge.other(current);
// Use the union-find structure to join the partitions of the current
// vertex and the vertex at the other side of the edge.
bool success = partition.joinPartitions(other, current);
// If the partitions were joined successfully, no action is needed.
if (success) {
continue;
}
// If the vertices were already in the same partition,
// a loop has been found. Create a new face and travel rightwards
// from the current edge to apply the face index to all edge
// that are enclosing the face.
faces.emplace_back();
f_index_t faceIndex = faces.size() - 1;
applyFaceRightwards(other, current, faceIndex);
}
}
// Add all unprocessed edges to the outer plane.
for (size_t i = 1; i < edges.size(); i++) {
if (edges[i].faceRight == OUTER_PLANE) {
faces[OUTER_PLANE].edges.push_back(i);
}
}
}
一旦您构建了人脸图,您就可以在人脸图上使用 BFS,如您在问题中所述。对于每个面,您将遍历包围该面的边。对于每条边,您将获得双边并将双边引用的面添加到 BFS 队列中。您将记录每个人脸的 BFS 等级。要找到在洪水期间可能会倒塌的墙,您需要确定不同 BFS 等级的面之间的边。
请注意,上面给出的算法有一个限制:它实际上并没有构建全脸图,因为它没有考虑循环嵌套。考虑以下示例:
全脸图将有一个顶点用于外平面,然后一个顶点用于第一个和第二个正方形之间的区域,然后一个顶点用于内部正方形中的区域:
然而,上面给出的算法并没有检查内部矩形是否被外部矩形包围。相反,它将两个矩形都指定为被外平面包围:
所以如果你真的想构建全脸图,这个结果是错误的。例如,您可以通过添加第二个扫描通道来跟踪面部嵌套来解决此问题。然而,对于 FLOOD 问题,这是不需要的:对于最终结果,我们对具体的 BFS 级别不感兴趣,而只对相邻面之间是否存在 BFS 级别差异感兴趣。要获得此信息,内矩形的面是附加到外矩形的面还是附加到外平面都没有关系。用更实际的术语来说:如果一个结构完全封闭在另一个结构内,则内部结构的壁将按照结构移动到外平面时的顺序破裂。当将结构移动到外平面时,它的壁当然会更快地破裂,但它们破裂的顺序是相同的。因此最终结果不会改变。
这使我们不必编写额外的代码来处理人脸嵌套,并使我们的代码更加紧凑。
完整代码(SPOJ 100%'d):
#include <algorithm>
#include <array>
#include <cstdint>
#include <iostream>
#include <numeric>
#include <queue>
#include <vector>
/**
* Aliases to make it clear which object type is referenced by an index.
*/
using v_index_t = uint32_t;
using e_index_t = uint32_t;
using f_index_t = uint32_t;
/**
* The edge index 0 is used to indicate "no edge".
*/
constexpr e_index_t NULL_EDGE = 0;
/**
* The face index 0 is used to indicate the outer plane.
*/
constexpr f_index_t OUTER_PLANE = 0;
/**
* The four direction used in the axis-aligned input graph.
*/
enum Direction { Up, Right, Down, Left };
/**
* Data structure for a vertex.
*/
struct Vertex {
/**
* Position of the vertex.
*/
const int x;
const int y;
/**
* The edges incident to the vertex in the four direction.
* If the vertex does not have an edge for some direction, NULL_EDGE is used.
*/
std::array<e_index_t, 4> incident;
/**
* Create a new vertex at the given position.
*/
Vertex(int x, int y);
/**
* Get the direction that vertex "to" is, relative to this vertex.
*/
Direction getDirection(const Vertex &to) const;
};
/**
* Data structure for an edge, using the DCEL approach
*/
struct Edge {
/**
* Starting vertex of the edge.
*/
v_index_t a;
/**
* End vertex of the edge.
*/
v_index_t b;
/**
* The peer edge, meaning the directed edge b->a.
*/
e_index_t peer;
/**
* The face to the right of the edge
* (from the perspective of the starting vertex).
*/
f_index_t faceRight{OUTER_PLANE};
/**
* Default-constructed, needed for the NULL_EDGE.
*/
Edge() = default;
/**
* Create a new edge a->b.
*/
Edge(v_index_t a, v_index_t b);
/**
* Get the other vertex of the edge. If "from" is "a" then "b" is returned,
* if "from" is "b" then "a" is returned.
*/
v_index_t other(v_index_t from) const;
};
/**
* Data structure for a face (meaning a region in the plane).
*/
struct Face {
/**
* List of edges that enclose this face.
*/
std::vector<e_index_t> edges;
};
/**
* Data structure for a graph.
*/
struct Graph {
/**
* List of vertices in the graph.
*/
std::vector<Vertex> vertices;
/**
* List of edges in the graph.
*/
std::vector<Edge> edges;
/**
* List of faces in the graph.
*/
std::vector<Face> faces;
/**
* Create the empty graph.
*/
Graph();
/**
* Add an undirected edge a<->b.
*/
void add_edge(v_index_t a, v_index_t b);
/**
* Get the right-most edge that follows the edge parent->current.
*/
e_index_t getRightMostEdge(v_index_t current, v_index_t parent);
/**
* Sort the vertices by their coordinate.
*/
std::vector<v_index_t> getSortedVertices();
/**
* Travel right-wards along the successor edges of from->to.
* Apply the faceIndex to each visited edge.
*/
void applyFaceRightwards(v_index_t to, v_index_t from, f_index_t faceIndex);
/**
* Use sweep-lining to construct the face graph.
*/
void createFaceGraph();
/**
* Use BFS on the face graph to identify which collapsing walls.
*/
void findCollapsedWalls();
};
/**
* Union-find data structure.
*/
struct VertexPartition {
/**
* Maps each vertex to the representive of its partition.
*/
std::vector<v_index_t> parent;
/**
* Tracks the rank of each vertex in the union-find tree structure.
*/
std::vector<int> rank;
/**
* Initialize the union-find structure such that each vertex
* is in its own partition.
*/
VertexPartition(size_t size);
/**
* Find the representive of the partition in which the vertex is contained.
*/
v_index_t findPartition(v_index_t vertex);
/**
* Join the partitions containing the vertices a and b.
*/
bool joinPartitions(v_index_t a, v_index_t b);
};
Vertex::Vertex(int x, int y) : x{x}, y{y}, incident{} {}
Direction Vertex::getDirection(const Vertex &to) const {
bool left = (x > to.x);
bool right = (x < to.x);
bool up = (y < to.y);
bool down = (y > to.y);
if (!up && !down) {
if (right) {
return Direction::Right;
} else if (left) {
return Direction::Left;
}
}
if (!left && !right) {
if (up) {
return Direction::Up;
} else if (down) {
return Direction::Down;
}
}
throw std::runtime_error{"Invalid alignment!"};
}
Edge::Edge(v_index_t a, v_index_t b) : a{a}, b{b} {}
v_index_t Edge::other(v_index_t from) const {
if (from == a) {
return b;
}
if (from == b) {
return a;
}
throw std::runtime_error{"Vertex not incident to edge!"};
}
VertexPartition::VertexPartition(size_t size) : parent(size), rank(size) {
std::iota(parent.begin(), parent.end(), 0);
}
v_index_t VertexPartition::findPartition(v_index_t vertex) {
// Pass 1: Find the representive of the partition containing vertex
v_index_t top = vertex;
while (parent[top] != top) {
top = parent[top];
}
// Pass 2: Compress path to speed up future queries.
while (parent[vertex] != vertex) {
v_index_t next = parent[vertex];
parent[vertex] = top;
vertex = next;
}
return top;
}
bool VertexPartition::joinPartitions(v_index_t a, v_index_t b) {
v_index_t aParent = findPartition(a);
v_index_t bParent = findPartition(b);
if (aParent == bParent) {
return false;
}
// Make the representive with maximum rank the representive of the joined
// partition.
int aParentRank = rank[aParent];
int bParentRank = rank[bParent];
if (aParentRank < bParentRank) {
v_index_t tmp = aParentRank;
aParentRank = bParentRank;
bParentRank = tmp;
}
parent[bParent] = aParent;
if (aParentRank == bParentRank) {
rank[aParent] += 1;
}
return true;
}
Graph::Graph() {
faces.emplace_back(); // Create default face, representing the outer plane.
edges.emplace_back(); // Create null edge, so that index 0 means "no edge".
}
void Graph::add_edge(v_index_t a, v_index_t b) {
edges.emplace_back(a, b);
edges.emplace_back(b, a);
e_index_t indexAtoB = edges.size() - 2;
e_index_t indexBtoA = edges.size() - 1;
edges[indexAtoB].peer = indexBtoA;
edges[indexBtoA].peer = indexAtoB;
Direction directionAtoB{vertices[a].getDirection(vertices[b])};
Direction directionBtoA{vertices[b].getDirection(vertices[a])};
vertices[a].incident[directionAtoB] = indexAtoB;
vertices[b].incident[directionBtoA] = indexBtoA;
}
e_index_t Graph::getRightMostEdge(v_index_t parent, v_index_t current) {
Direction direction{vertices[current].getDirection(vertices[parent])};
// Check the directions following the current direction in counter-clockwise
// order. Note that in the last iteration of the loop, the direction
// current->parent is checked.
for (int i = 1; i <= 4; i++) {
int directionAsIntShifted{direction - i};
if (directionAsIntShifted < 0) {
directionAsIntShifted += 4;
}
Direction directionShifted{static_cast<Direction>(directionAsIntShifted)};
e_index_t edge{vertices[current].incident[directionShifted]};
if (edge != NULL_EDGE) {
return edge;
}
}
return NULL_EDGE;
}
std::vector<v_index_t> Graph::getSortedVertices() {
std::vector<v_index_t> indices(vertices.size());
std::iota(indices.begin(), indices.end(), 0);
auto key = [&](v_index_t i, v_index_t j) -> bool {
return (vertices[i].y < vertices[j].y) ||
(vertices[i].y == vertices[j].y && vertices[i].x < vertices[j].x);
};
std::sort(indices.begin(), indices.end(), key);
return indices;
}
void Graph::applyFaceRightwards(v_index_t current, v_index_t parent,
f_index_t faceIndex) {
for (;;) {
e_index_t edgeIndex{getRightMostEdge(parent, current)};
if (!edgeIndex) {
break;
}
Edge &edge{edges[edgeIndex]};
v_index_t next{edge.other(current)};
if (edge.faceRight != OUTER_PLANE) {
break;
}
edge.faceRight = faceIndex;
faces[faceIndex].edges.push_back(edgeIndex);
parent = current;
current = next;
}
}
void Graph::createFaceGraph() {
// Sort vertices by coordinate
auto indices{getSortedVertices()};
// Initialize union-find data structure with one partition for each vertex.
VertexPartition partition(vertices.size());
// Sweep over the sorted vertices
for (v_index_t current : indices) {
// Check leftwards and downwards edges. The rightwards and upwards edges
// are handled from the vertex at the other side.
for (Direction direction : {Direction::Left, Direction::Down}) {
e_index_t edgeIndex{vertices[current].incident[direction]};
if (edgeIndex == NULL_EDGE) {
continue;
}
Edge &edge{edges[edgeIndex]};
v_index_t other = edge.other(current);
// Use the union-find structure to join the partitions of the current
// vertex and the vertex at the other side of the edge.
bool success = partition.joinPartitions(other, current);
// If the partitions were joined successfully, no action is needed.
if (success) {
continue;
}
// If the vertices were already in the same partition,
// a loop has been found. Create a new face and travel rightwards
// from the current edge to apply the face index to all edge
// that are enclosing the face.
faces.emplace_back();
f_index_t faceIndex = faces.size() - 1;
applyFaceRightwards(other, current, faceIndex);
}
}
// Add all unprocessed edges to the outer plane.
for (size_t i = 1; i < edges.size(); i++) {
if (edges[i].faceRight == OUTER_PLANE) {
faces[OUTER_PLANE].edges.push_back(i);
}
}
}
void Graph::findCollapsedWalls() {
// Perform BFS on face graph
std::queue<f_index_t> queue{};
std::vector<bool> visited(faces.size());
std::vector<int> rank(faces.size());
auto processFace = [&](f_index_t next, int nextRank) {
if (visited[next]) {
return;
}
queue.push(next);
visited[next] = true;
rank[next] = nextRank;
};
processFace(OUTER_PLANE, 0);
while (!queue.empty()) {
f_index_t current{queue.front()};
queue.pop();
int nextRank = rank[current] + 1;
for (e_index_t edge : faces[current].edges) {
e_index_t peer = edges[edge].peer;
processFace(edges[peer].faceRight, nextRank);
}
}
// Check which edges are next to faces with equal BFS rank
auto isWallStanding = [&](e_index_t i) {
e_index_t j = edges[i].peer;
return rank[edges[i].faceRight] == rank[edges[j].faceRight];
};
// Print the final result. Since the edge list contains each edge
// twice, once for each direction, we skip over every second edge.
// We also skip the NULL_EDGE.
size_t wallCount = 0;
for (size_t i = 2; i < edges.size(); i += 2) {
if (isWallStanding(i)) {
wallCount++;
}
}
std::cout << wallCount << std::endl;
for (size_t i = 2; i < edges.size(); i += 2) {
if (isWallStanding(i)) {
std::cout << i / 2 << std::endl;
}
}
}
int main() {
Graph graph{};
// Read input graph from stdin
size_t corners{};
std::cin >> corners;
for (size_t i = 0; i < corners; i++) {
int x;
int y;
std::cin >> x >> y;
graph.vertices.emplace_back(x, y);
}
size_t walls{};
std::cin >> walls;
for (size_t i = 0; i < walls; i++) {
size_t indexA;
size_t indexB;
std::cin >> indexA >> indexB;
graph.add_edge(indexA - 1, indexB - 1);
}
graph.createFaceGraph();
graph.findCollapsedWalls();
return 0;
}