OpenJudge: Percolation 渗透

总时间限制: 1000ms  内存限制: 32768kB

描述

定义一个N行N列的矩阵,矩阵中的每个元素是个方格,每个方格有两种可能的状态:开通的或关闭的。初始时,所有方格都是关闭的。输入数据的每一步会指定矩阵中某个原来关闭的方格变成开通的。要求编写程序判断当前是否存在从矩阵中最上面一行的任何一个开着的方格走到最下面一行的任何一个开着的方格的路径。如果存在的话,输出当前的步数。比如走到第14步时,矩阵变成上下通透的,那么就输出14。注意:输入数据中只会把矩阵中的一部分方格打开。如果所有步骤都执行完了,矩阵仍然不是上下通透的,那么输出-1。显然,矩阵变成上下通透的一个必要条件是:最上面一行和最下面一行都至少要有一个方格是打开的。

在矩阵中行走时,只能横着走或竖着走,不能斜着走,也不能走出矩阵的边界。

输入

输入的第一行是一个自然数T(1≤T≤10),代表测试数据的组数。每组测试数据的第一行有两个自然数N和M,其中N(1≤N≤1,000)代表方阵的维度,M(1≤M≤N*N)代表本组测试中打开的方格数目。随后的M行中每行有两个自然数,分别代表所打开的方格的行、列下标。注意:本题中矩阵的下标从1开始,即所有下标的取值都是[1, N]区间中的正整数。

输出

每组测试数据输出一个自然数K,表示打开第K个方格后,矩阵变成上下通透的。如果M个方格都打开后,矩阵仍然不是上下通透的,那么输出-1。

样例输入

1
4 10 
2 2
3 1
4 2
4 4
1 2
2 3
2 1
3 2
3 4
3 1

样例输出

8

分析

  1. 由于需要判断在第几个格子开通后,整个图就percolation了,所以不能使用搜索的办法。只能使用并查集,将连通的格子放到一个集合中
  2. 最开始的想法是给每个格子赋予两个状态,判断其是否为“上通透”和"下通透"的。如果一个格子既是上通透的,又是下通透的,那么整张图一定是通透的。但是改变状态的过程太复杂了,我需要修改当前点和他四个neighbour的上下两个状态,而且还要进一步延申到四个neighbour所在集合的所有点的状态。即使只改四个neighbour所在集合的根节点的状态,这个状态在并查集压缩过程中还要向上传递……好复杂好复杂
  3. 然后的想法是,每次开通一个点之后,我只要遍历最上一排点和最下一排点,看他们是否有公共的父结点即可。这样我需要用一个unordered_set/set记录最上一排点的父结点,然后把最下一排点的所有父结点在这个unordered_set/set中查询一下就好了。然后发现C++对类对象使用unordered_set/set需要重载hash函数或者<运算符,即使完成这个问题还是超时了,时间复杂度O(mn)
  4. 最后发现只要设计两个虚拟点A和B,所有最上一排的点都和A相连,所有最下一排点都和B相连。每次插入元素时,只要判断A和B是不是有公共父亲就好了。时间复杂度O(m)(不考虑并查集复杂度)
  5. 然后。。。mmp因为第4条的括号,没考虑并查集的复杂度,爆内存了。。。原来是新插入的结点不能作为上下左右4个neighbour的父节点,这样可能会连成一条链,导致并查集递归查询的时候超内存了。
  6. 总结这道题的解题过程,真是rlgl,菜得离谱,不知道自己在想啥,重载hash函数这种东西怎么可能出现在oj里呢。。。
​
#include <iostream>
using namespace std;

const int N = 1000005;
int f[N] = {};
bool open[N] = {};

int find(int d) {
    if (d != f[d]) {
        f[d] = find(f[d]);
    }
    return f[d];
}

void connect(int index, int nindex) { f[find(index)] = find(nindex); }

bool is_valid(int nx, int ny, int n) {
    return (nx >= 1) && (nx <= n) && (ny >= 1) && (ny <= n);
}

int handle_case() {
    int n = 0, m = 0;
    scanf("%d%d", &n, &m);
    for (int i = 0; i <= n * n + 1; ++i) {
        f[i] = i;
        open[i] = false;
    }

    int neighbours[4][2] = {-1, 0, 1, 0, 0, -1, 0, 1};
    int step = -1;
    for (int iter = 1; iter <= m; ++iter) {
        // insert a node
        int x = 0, y = 0;
        scanf("%d%d", &x, &y);
        if (step > 0) {
            continue;
        }
        int index = (x - 1) * n + y;
        open[index] = true;
        if (x == 1) {
            f[index] = 0;
        } else if (x == n) {
            f[index] = n * n + 1;
        }

        // union with neighbours
        for (int i = 0; i < 4; ++i) {
            int nx = x + neighbours[i][0];
            int ny = y + neighbours[i][1];
            int nindex = (nx - 1) * n + ny;
            if (is_valid(nx, ny, n) && open[nindex]) {
                connect(index, nindex);
            }
        }

        if (find(0) == find(n * n + 1)) {
            step = iter;
        }
    }
    return step;
}

int main() {
#ifndef ONLINE_JUDGE
    freopen("in.txt", "r", stdin);
#endif
    int t = 0;
    scanf("%d", &t);
    while (t--) {
        int a = handle_case();
        printf("%d\n", a);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值