|
|
(出题人 $Brian Dean$ 题解 $yuan$译)
此问题是一种递归(深度优先)搜索以识别图形中的连接组件的练习。 第一项任务比第二项任务简单得多。我们建一个图,其中每个单元格是一个结点,如果它们包含相同的数字,则两个相邻的单元格连接一条边。 然后,我们从每个单元格开始递归 $Flood$ 填充(跳过已经访问过的单元格),扩展每个联通区域,并累加联通区域的大小。
对于第二项任务,我们为每对奶牛$(x,y)$创建一个图,结点是我们在第一个任务中计算的区域,用一条边连接相邻区域, 其中一个区域用 $x$ 标记,另一个区域用 $y$ 标记。 图中的每个结点被赋予一个“权”值,其权值与第一个任务的相应区域的大小相同,然后我们在每个图形上开始递归 $Flood$ 填充以找到具有最大面积的任何一个。
这里有两个重要的想法可以使我们的解决方案快速运行。 一是确保第一个任务中的每个区域在第二个任务中都被压缩为单个结点。 如果我们不这样做,那么在第二个任务中,我们可能会反复递归扫描同一大片区域,浪费大量时间。 另一个方法是,当我们进行递归搜索时,我们需要确保运行时间仅依赖于图中的边数而不是结点数,因为第二个任务图中涉及大量结点,但可能边数很少。 例如,我们不希望为每个结点保留一个“是否遍历过”的标记数组,并且在每个递归搜索时都被初始化为 $false$。 相反,在下面的解决方案中,我们为此使用了映射数据结构,它只在需要时创建标志,避免初始化不相关结点的初始化标志。
每个数字块是一个整体,用并查集维护大小 ; 给所有相邻数字块建边 ,并根据颜色排序 ; 处理每个数字块与哪些数字块相连,维护一个颜色连接着哪些数字块; 枚举处理所有相邻的数字块方案,已经根据数字值排在一起;
$Brian$ $Dean$ 的代码如下: #include <iostream>
#include <fstream>
#include <map>
#include <set>
#include <algorithm>
using namespace std;
int N, B[1001][1001], cellid[1001][1001], global_regid;
struct Graph {
map<int,set<int>> out_edges;
map<int,int> nodesize, regid; // size of each node and region ID to which it belongs
map<int,int> regsize; // size of each region
};
typedef pair<int,int> pii;
map<int, Graph> G1; // Graphs for all possible single IDs
map<pii, Graph> G2; // Graphs for all possible adjacent region pairs
// Return size of region reachable from nodeid
int visit(Graph &G, int nodeid, int regid)
{
if (G.regid.count(nodeid) > 0) return 0; // already visited? bail out
G.regid[nodeid] = regid; // mark this node as visited
int a = G.nodesize[nodeid]; // tally up region size
for (int nbrid : G.out_edges[nodeid])
a += visit(G, nbrid, regid);
G.regsize[regid] = a;
return a;
}
// Compute region sizes and return largest region size in graph.
// Running time only depends on # of edges, not # of nodes, so graph can be very sparse
int largest_region(Graph &G)
{
int m = 0;
for (auto &p : G.out_edges) m = max(m, visit(G, p.first, ++global_regid));
return m;
}
void add_edge(Graph &G, int node1, int node2)
{
G.out_edges[node1].insert(node2);
G.out_edges[node2].insert(node1);
G.nodesize[node1] = G.nodesize[node2] = 1;
}
// Add edge between two regions in a region pair graph
void add_G2_edge(int i1, int j1, int i2, int j2)
{
int b1 = B[i1][j1], b2 = B[i2][j2], c1 = cellid[i1][j1], c2 = cellid[i2][j2];
if (b1 > b2) { swap (b1,b2); swap(c1,c2); }
int r1 = G1[b1].regid[c1], r2 = G1[b2].regid[c2];
pii p = make_pair(b1, b2);
add_edge(G2[p], r1, r2);
G2[p].nodesize[r1] = G1[b1].regsize[r1];
G2[p].nodesize[r2] = G1[b2].regsize[r2];
}
int main()
{
ifstream fin ("multimoo.in");
ofstream fout ("multimoo.out");
fin >> N;
for (int i=1; i<=N; i++)
for (int j=1; j<=N; j++) {
fin >> B[i][j];
cellid[i][j] = i*N+j; // unique ID for each cell
}
// Build primary graph
for (int i=1; i<=N; i++)
for (int j=1; j<=N; j++) {
add_edge(G1[B[i][j]],cellid[i][j],cellid[i][j]);
if (i<N && B[i+1][j] == B[i][j]) add_edge(G1[B[i][j]], cellid[i][j], cellid[i+1][j]);
if (j<N && B[i][j+1] == B[i][j]) add_edge(G1[B[i][j]], cellid[i][j], cellid[i][j+1]);
}
// Find largest region in primary graph
int answer1 = 0;
for (auto &p : G1) answer1 = max(answer1,largest_region(p.second));
// Build secondary graphs based on regions of the primary graph that are adjacent
for (int i=1; i<=N; i++)
for (int j=1; j<=N; j++) {
if (i<N && B[i+1][j] != B[i][j]) add_G2_edge(i,j,i+1,j);
if (j<N && B[i][j+1] != B[i][j]) add_G2_edge(i,j,i,j+1);
}
// Find largest region in secondary graphs
int answer2 = 0;
for (auto &p : G2) answer2 = max(answer2, largest_region(p.second));
fout << answer1 << "\n";
fout << answer2 << "\n";
return 0;
}
题目3045 [USACO Open18 Silver] Multiplayer Moo
AAAAAAAAAA
8
评论
2022-11-02 20:22:34
|
|
|
因为边最多有 $100$ 条,所以有效的节点编号最多只有 $200$ 个,考虑离散化。 设离散化之后的 $P \times P$ 的邻接矩阵 $A$ ,那么不难想到这个邻接矩阵经过两条边的最短路就是 $\displaystyle B[i, j] = \min_{1 \le k \le P} {A[i, k] + A[k, j]} \notag $ 紧接着,经过三条边的最短路也不难想到。 $\displaystyle C[i, j] = \min_{1 \le k \le P} {A[i, k] + B[k, j]} \notag $ 联想到什么了吗?像乘法一样!实际上,求经过 $n$ 条边的最短路等价于一个广义“矩阵乘法”,用 $A^m$ 表示经过 $m$ 条边的最短路的话,就能表示出来一般的式子: $\displaystyle A^{a + b}[i, j] = \min_{1 \le k \le P} {A^a[i, k] + A^b[k, j]} \notag $ 于是,求几条边就相当于求“几次幂”,求幂的话当然是快速幂了。 具体到这个广义的“矩阵乘法”快速幂的话,一次“矩阵乘法”就类似于进行一次 $\mathrm{floyd}$。
题目1470 [USACO Nov07] 奶牛接力
AAAAAAAAAA
8
评论
2022-10-31 22:09:30
|
|
|
题目1312 [HAOI 2007]覆盖问题
8
评论
2022-10-30 22:56:53
|
|
|
这题实际上相当于在一条 $1$ ~ $n$ 的路径,找到两个点 $a, b$(先经过 $a$),使得 $w(b) - w(a)$ 最大。 可以用 SPFA 算法维护从节点 $1$ 到其它节点的点权值的最小值,以及节点 $n$ 到该节点的点权值的最大值,具体实现时候,用 $\min(min(u), w(v))$ 代替 $min(u) + w(v)$ 进行松弛就可以了。 最后枚举所有的节点,找到 $\displaystyle \max_{1 \le i \le n}(max(i) - min(i))$ 。
题目406 [NOIP 2009]最优贸易
AAAAAAAAAA
8
评论
2022-10-27 15:06:15
|
|
|
题目3642 [HDOJ 3686]交通实时查询系统
6
评论
2022-10-27 11:47:13
|
|
|
上述题目实际可以简化为:求一个节点 $1$ 到节点 $n$ 的路径,第 $k + 1$ 大的边权最小。是一个贪心的思想,很容易得出。 我们用 $f[u, k]$ 表示在节点 $1$ 到节点 $u$ 的路径中,用了 $k$ 次免费机会时,路径上最大的边权的最小值。 那么对于一个边 $u \to v$ 有以下状态转移:
这个状态转移显然有后效性,因为题目给出的并不一定是 DAG,确定不了遍历顺序。对于这种情况,我们使用 SPFA 来进行状态的转移。这样的最短路问题被称为 分层图最短路。用 SPFA 算法的时间复杂度是 $O(tNP)$,其中 $t$ 应该是一个比较小的常数,实际可以 AC 这一题。
题目147 [USACO Jan08] 架设电话线
AAAAAAAAAAAAA
9
评论
2022-10-27 11:21:43
|