点格棋
题目链接:LOJ 3378
题目大意
有一个 (n+1)*(m+1) 的格点组成的网格,然后两个人轮流操作,选两个相邻(距离为 1)且没有连边的点对连一个竖直或者水平的线段。
然后如果一个人连线之后一个新的位置的四个边界都有线段了,那这个人就获得一分,并要继续操作。
然后无法操作时结束,然后给你当前的局势,问你从现在开始算分,先手的分减去后手的分的最大值。
保证当前局势满足每个格子的四个边界都有 2 个或者 4 个线段。
思路
会发现每个格子看做点,那也就是说每个点要么没有边,要么有两条边。
那就是形成了一些环,以及单点。
那单点在之前就贡献了,所以完全不用管。
但是其实不是,它有边界,那边界就相当于一条边,所以会有环和链。
那不如先考虑只有链怎么做。
那会发现先手一定会给后手创造机会,那如果只有一个连通块,那后手就能把所有的都取完。
那如果不止一个连通块,后手选完这个连通块的所有点,就要变成了先手,再开一个连通块。
不过会发现这样无法解释第二个样例,因为其实你不一定要选完。
那怎么让自己停下来呢?就是要少贪点,比如对于链我们可以留下一个大小为 \(2\) 的,如果是环,那就至少要留下两个大小为 \(2\) 的(也就是要留 \(4\) 的大小)
那显然多留是不优的,你肯定是在你能拿且保住后手的基础上尽量多拿,否则如果不保后手为啥不全取完。
那考虑一些特别的情况,比如链长度为 \(2\),环大小为 \(4\)。
发现如果链长度是 \(2\),先手是可以不让后手保住后手的(就选中间的那个),但是环就没办法。
那我们就有状压 DP 是每个连通块是否处理,处理这些后手的最小费用(我用的是这个,也可以先手的最大费用)
那考虑优化,注意到对于所有的链,先手肯定会优先选长度最小的,因为先手是亏的。
那环也是这样。
那至少我们发现链,环,它们自己的顺序是固定的。
那就可以 \(f_{i,j}\) 来 DP,复杂度就是 \(O(n^2*n^2)\)(连通块数量是 \(n^2\))
那 \(n=20\) 肯定能过。
代码
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 24;
const int M = N * N;
int n, m, id[N][N], tot, fa[M], sz[M];
bool lr[N][N], ud[N][N], cir[M];
int a[M], b[M], f[M][M];
int find(int now) {
if (fa[now] == now) return now;
return fa[now] = find(fa[now]);
}
void add(int x, int y) {
if (find(x) == find(y)) {cir[find(x)] = 1; return ;}
sz[find(y)] += sz[find(x)]; fa[find(x)] = find(y);
}
bool cmp(int x, int y) {
return x > y;
}
int main() {
scanf("%d %d", &n, &m);
for (int i = 0; i <= n; i++)
for (int j = 1; j <= m; j++)
scanf("%1d", &lr[i][j]);
for (int i = 1; i <= n; i++)
for (int j = 0; j <= m; j++)
scanf("%1d", &ud[i][j]);
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++) {
if (lr[i - 1][j] && lr[i][j] && ud[i][j - 1] && ud[i][j]) continue;
id[i][j] = ++tot;
}
for (int i = 1; i <= tot; i++) fa[i] = i, sz[i] = 1;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++) {
if (lr[i - 1][j] && lr[i][j] && ud[i][j - 1] && ud[i][j]) continue;
if (i > 1 && !lr[i - 1][j]) add(id[i][j], id[i - 1][j]);
if (j > 1 && !ud[i][j - 1]) add(id[i][j], id[i][j - 1]);
}
for (int i = 1; i <= tot; i++) if (fa[i] == i)
if (cir[i]) b[++b[0]] = sz[i];
else a[++a[0]] = sz[i];
sort(a + 1, a + a[0] + 1, cmp);
sort(b + 1, b + b[0] + 1, cmp);
memset(f, 0x7f, sizeof(f));
f[0][0] = 0;
for (int i = 0; i <= a[0]; i++)
for (int j = 0; j <= b[0]; j++) {
if (i < a[0]) {
int val = a[i + 1] - f[i][j];
if (a[i + 1] > 2) val = max(val, (a[i + 1] - 2) - 2 + f[i][j]);
f[i + 1][j] = min(f[i + 1][j], val);
}
if (j < b[0]) {
int val = b[j + 1] - f[i][j];
if (b[j + 1] >= 4) val = max(val, (b[j + 1] - 4) - 4 + f[i][j]);
f[i][j + 1] = min(f[i][j + 1], val);
}
}
printf("%d", -f[a[0]][b[0]]);
return 0;
}
标签:return,val,LOJ,点格棋,id,int,now,find,DP
From: https://www.cnblogs.com/Sakura-TJH/p/LOJ_3378.html