Codeforces Round #634 (Div. 3) F. Robots on a Grid(基环树+倍增)

原题链接:F. Robots on a Grid


题目大意:


给出一个 n × m n \times m n×m 的网格,每一个格子只会是黑色(用 0 0 0 表示)或者白色(用 1 1 1 表示),并且每一个格子还会有一个方向(上下左右),保证方向不指向矩阵外。你可以在这个网格中任意放置一些机器人,每一时刻,这些机器人会同时在网格中进行移动。

你要遵循以下规则来决定机器人的放置:

  • 每个机器人每一回合都要移动,在一次移动过程中,根据当前方向移动到相邻的单元格。
  • 在放置机器人时,不能在同一个单元格中放置多个机器人,且必须确保在移动前后不会有多个不同的机器人占据同一个单元格。
  • 注意,机器人会沿进行无限次的移动。

现在询问你,在满足上述条件下,最多能在网格中放置多少个机器人,以及最多能在多少个个黑色格子上放置机器人。

解题思路:


如果我们把每一个格子看作一个点,每一个方向看作一条有向边,那么这个矩阵就是 n × m n \times m n×m 个点, n × m n \times m n×m 条边的有向图。

想一想这一张图会是什么样子的。

要么是一个环,要么就是一个环上带一些链,一定不会存在只是一条链的情况(因为每个点都一定会有一个出边)。

那么我们的这一张图就形成了一个基环树森林。

先从环入手,假设我们在所有的格子内都放了一个机器人,在无限的行动下,我们所有的机器人一定都会挤到每个基环树的环内。环内的机器人数量一定会大于或等于环的大小。

但是每个机器人不能同在一个点上,那么第一问就很显然了。假设每个环的大小是 s i z siz siz ,我们最多可以放 ∑ s i z \sum siz siz 个机器人。
 

想想第二问怎么办,给出一张图:

在这里插入图片描述
我们要尽可能多地在黑点上放机器人,假设我们往所有黑点上都放了机器人,那么会出现这种情况:

  • 移动一步时,我们的 c c c 号点和 b b b 号点会到同一个点上,显然 c c c b b b 我们只能选一个点来放,我们选哪一个放都无所谓。
  • 同理,移动两步时,我们的 a a a 号点和 e e e 号点会到同一个点上, a a a e e e 我们选哪一个放都无所谓。
  • 其他的黑点不造成影响,我们直接放就好了。

由于我们的机器人是同步移动的,因此移动到同一个点的所有机器人,我们只能选一个来放。那么我们只要看环上在无数次移动后的一个瞬间,每个格子上是否有黑色点来的机器人,选出其中一个能到这个点的即可。

我们发现,由于这个图是一个基环树森林,要使得所有机器人都移动到环内,最差情况下整张图会是一个环,那么只需要让每个机器人都移动 n × m n \times m n×m 次即可。

当机器人移动到环内后,我们每次移动所造成的影响只是换了个位置,而没有进行改变,本质上和没有移动的状态是一样的。

而对于答案而言,我们只需要知道在环内的每个点上是否有黑色格子机器人可以到达即可。

让每个机器人移动 n × m n \times m n×m 次这个操作,我们用倍增来实现就好了。

具体解释看代码即可。

时间复杂度: O ( n m log ⁡ n m ) O(nm \log nm) O(nmlognm)

AC代码:


#include <bits/stdc++.h>
#define YES return void(cout << "Yes\n")
#define NO return void(cout << "No\n")
using namespace std;

using u64 = unsigned long long;
using PII = pair<int, int>;
using i64 = long long;

const int N = 1e6 + 1;

int nxt[N][21];

void solve() {

    int n, m;
    cin >> n >> m;

    //我们把每个格子都用一个编号: (i - 1) * m + j 表示
    vector<int> col(n * m + 1);
    for (int i = 1; i <= n; ++i) {
        string str; cin >> str;
        for (int j = 1; j <= m; ++j) {
            col[(i - 1) * m + j] = str[j - 1] - '0';
        }
    }

    for (int i = 1; i <= n; ++i) {
        string str; cin >> str;
        for (int j = 1; j <= m; ++j) {
            int tx = i + (str[j - 1] == 'D') - (str[j - 1] == 'U');
            int ty = j + (str[j - 1] == 'R') - (str[j - 1] == 'L');
            //先处理每个点移动 1 次(即2^0次)能到的点
            nxt[(i - 1) * m + j][0] = (tx - 1) * m + ty;
        }
    }

    int mxN = n * m;//总点数

    //在处理出倍增表 跳 2^1,2^2,...,2^20 次
    for (int i = 1; i <= 20; ++i) {
        for (int j = 1; j <= mxN; ++j) {
            nxt[j][i] = nxt[nxt[j][i - 1]][i - 1];
        }
    }

    //b 代表最后这个格子上有多少个黑点
    //w 代表最后这个格子上有多少个白点
    vector<int> b(mxN + 1), w(mxN + 1);
	
	//假设每个格子上都放了一个机器人
    for (int i = 1; i <= mxN; ++i) {
        //先倍增跳 nm 步
        int pos = i;
        for (int j = 20; ~j; --j) {
            if (mxN >> j & 1) pos = nxt[pos][j];
        }
        //跳到的点加上自己的颜色
        if (!col[i]) b[pos]++;
        else w[pos]++;
    }

    int mx = 0, mxb = 0;
    for (int i = 1; i <= mxN; ++i) {
        mx += (b[i] != 0 || w[i] != 0);//这一点上有机器人 那么肯定是环中的点
        mxb += (b[i] != 0);//所有从黑色点来的机器人 我们选一个即可
    }

    cout << mx << ' ' << mxb << '\n';
}

signed main() {

    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);

    int t = 1; cin >> t;
    while (t--) solve();

    return 0;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

柠檬味的橙汁

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值