Loading

4.30 CW 模拟赛 T2. 贪吃蛇

思路

首先考虑对于每个点都做一遍太慢了, 考虑有没有什么传递性

找点性质
感受到一种性质
对于两个内部互相可吃的相邻连通块, 如果能够找到两个相邻点对 \(\{u, v\}, \{x, y\}\) \((\)其中 \(u, x\) 属于一个连通块, \(v, y\) 属于另一个\()\), 其中 \(u\) 对应的连通块内和大于 \(v\) 权值, \(y\) 对应的连通块内和大于 \(x\) 权值, 那么这两个连通块就可以合并

发现这样搞的太复杂, 换个性质

考虑一个点先把周围所有小于它的点都吃了, 然后考虑边界上的最小值, 如果边界最小值小于块内和, 那么可以继承边界最小值的结果, 否则出不去肯定为 \(0\)

现在的问题是维护任意一个点周围所有小于它的点的和, 及这个块的边界最小值

方法 \(1\): 在值域上从小到大扫, 离线思想

首先, 从小到大加入一个点时, 找他是否拥有相邻的已经被加过的块, 不难发现其为这个块的边界最小值
处理这个块能不能走出来, 不能直接设为 \(0\), 否则连边并将块合并到这个点上, 最终从确定答案的点\((\)全局最大值\()\)扫过来即可

代码
#include <bits/stdc++.h>
#define int long long
const int MAXN = 2e6 + 20;
const int dx[5] = {0, 0, -1, 1}, dy[5] = {1, -1, 0, 0};

namespace FastIO {
    __int128 read() {
        __int128 x = 0, f = 1;
        char ch = getchar();
        while (ch < '0' || ch > '9') {
            (ch == '-') && (f = -1);
            ch = getchar();
        }
        while (ch >= '0' && ch <= '9') x = x * 10 + ch - '0', ch = getchar();
        return x * f;
    }
} using namespace FastIO;

int cnt = 0, n, m;
bool ans[MAXN];
std::pair<int, int> back[MAXN];
bool vis[MAXN];

struct node {
    __int128 val;
    int x, y;
    bool operator<(const node &rhs) const { return val < rhs.val; }
} a[MAXN];

struct UnionSet {
    UnionSet(int siz) { for (int i = 1; i <= siz; i++) fa[i] = i, w[i] = a[i].val; }
    int fa[MAXN];
    __int128 w[MAXN];
    int find(int x) { return fa[x] == x ? x : fa[x] = find(fa[x]); }
    void merge(int x, int y) {
        x = find(x), y = find(y);
        if (x == y) return;
        fa[y] = x; w[x] += w[y];
    }
};

std::vector<int> edge[MAXN];
void add(int u, int v) { edge[u].push_back(v); }

void dfs(int u, int ppp) { ans[u] = ppp; for (int v : edge[u]) { ans[v] = ppp; dfs(v, ppp); } }

signed main() {
    std::cin >> n >> m;
    std::vector<std::vector<int> > num(n + 5, std::vector<int>(m + 5, 0));

    __int128 val[n + 5][m + 5];
    __int128 mxval = -1;
    for (int i = 1; i <= n; i++) for (int j = 1; j <= m; j++) {
        a[++cnt].val = read();
        num[i][j] = cnt, val[i][j] = a[cnt].val, back[cnt] = {i, j};
        a[cnt].x = i, a[cnt].y = j;
        mxval = std::max(mxval, a[cnt].val);
    }
    
    UnionSet dsu(cnt);
    std::sort(a + 1, a + cnt + 1);

    for (int i = 1; i <= cnt; i++) {
        int x = a[i].x, y = a[i].y;
        for (int j = 0; j < 4; j++) {
            int nx = x + dx[j], ny = y + dy[j];
            int fav = dsu.find(num[nx][ny]), fau = dsu.find(num[x][y]);
            if (num[nx][ny] && val[nx][ny] <= val[x][y] && fav != fau) {
                if (dsu.w[fav] < val[x][y] && val[nx][ny] < val[x][y]) { dsu.merge(fau, fav); continue; } 
                if (val[nx][ny] == val[x][y]) { add(fau, fav); dsu.merge(fau, fav); continue; }
                add(fau, fav), dsu.merge(fau, fav);
            }
        }
    }
    for (int i = 1; i <= cnt; i++) {
        if (!vis[dsu.find(num[back[i].first][back[i].second])] && val[back[dsu.find(num[back[i].first][back[i].second])].first][back[dsu.find(num[back[i].first][back[i].second])].second] == mxval) {
            dfs(dsu.find(num[back[i].first][back[i].second]), 1);
            vis[dsu.find(num[back[i].first][back[i].second])] = true;
        }
    }
    for (int i = 1; i <= cnt; i++) std::cout << (int)(ans[i]) << ((i % m == 0) ? '\n' : ' ');
    
    return 0;
}

方法 \(2\): 神秘线段树合并

还不会, 咕咕咕

总结

一类对每个部分求出答案的问题, 往往需要找点传递性

这样的传递性有一个非常好的性质, 往往你只需要维护一个让整个图联通到确定答案的点的关系, 就可以维护整个图的答案
例如本题中的「边界最小值」, 不难发现每个点都拥有一个「边界最小值」, 而且这个「边界最小值」显然最终会连向一个确定答案的点\((\)全局最大值\()\)

posted @ 2025-05-05 21:06  Yorg  阅读(26)  评论(0)    收藏  举报