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\): 神秘线段树合并
还不会, 咕咕咕
总结
一类对每个部分求出答案的问题, 往往需要找点传递性
这样的传递性有一个非常好的性质, 往往你只需要维护一个让整个图联通到确定答案的点的关系, 就可以维护整个图的答案
例如本题中的「边界最小值」, 不难发现每个点都拥有一个「边界最小值」, 而且这个「边界最小值」显然最终会连向一个确定答案的点\((\)全局最大值\()\)

浙公网安备 33010602011771号