S O S D P
前置芝士:高维前缀和(SOS DP)。
如果不会的话左传题解区,包教包会 唔这里会讲一讲的(
简单来说,如何求一个一维序列的前缀和?
废话当然是直接累加啊……
那二维呢?(这里前缀和定义为每个维度都小于等于给定数的所有下标对应的值的和)
正常的写法是每个数用三个来推(也就是
sum[i][j]+=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1];
这种的,做的是原地前缀和,也就是说 sum 数组原来存的是原值),但我们发现这样的话一个式子里出现了
所以我们需要一个针对它的优化。
我们考虑我们二维前缀和实际上是要干一个什么事:求出一个前缀子矩阵里所有数的和。那我们不如先求出子矩阵每一列的和然后再把它们全部累起来(或者先行再列也无所谓),然后很容易能发现这两步都可以一维前缀和优化。具体来说,代码会长成这个样子:
首先对所有元素执行:sum[i][j]+=sum[i-1][j];
然后再对所有元素执行:sum[i][j]+=sum[i][j-1];
是不是很简洁,并且显然很好推广到高维?
这样的话,比如我们要对一个
好了回到本题。
首先如果我们把每种多米诺都给枚举出来并算出权值,建图,那么就转换成了要求:一个顶点的子集,大小为
这样的话点数大概会是
观察到这个图的一个特性:一个点的度数至多为
然后本题中
我们发现这个复杂度显然要炸,但是这个
然后这个复杂度实际上还是有点危,因为反向累前缀和那一步实际上是要分后半部分那个集合的大小来算的,于是我们发现实际上只需要保留大小
这样的话就可以了。肉眼观察一下前半部分放 int,但 unsigned 也够用了。
代码实际上并不难写挺难写的,所以还是贴一下吧(
#include <bits/stdc++.h>
#define fi first
#define se second
#define INF INT_MAX
#define cnt __builtin_popcount
#define int unsigned
using namespace std;
constexpr int N = 4e6 + 9, M = 50, W = 21, C = 9e6 + 9;
int n, k, tot, d, val[N], ans, sum;
pair<int, int> dm[N << 1];
int id(int x, int y) { return x * n + y; }
int getval(pair<int, int> x) { return val[x.fi] + val[x.se]; }
bool issim(pair<int, int> x, pair<int, int> y) {
return x.fi == y.fi || x.fi == y.se || x.se == y.fi || x.se == y.se;
}
int iadd(int x, int y) { return x == INF || y == INF ? INF : x + y; }
int f[1 << W], g[C], gst[M], gval[1 << W][9];
int num[C], ntot;
void dfs(int hbt, int rem, int x) {
if (!hbt)
num[ntot++] = x;
else {
dfs(hbt - 1, rem, x);
if (rem) dfs(hbt - 1, rem - 1, x | (1 << (hbt - 1)));
}
}
int pos(int x) { return lower_bound(num, num + ntot, x) - num; }
signed main() {
ios::sync_with_stdio(false), cin.tie(nullptr);
dfs(M - W, 8, 0), cin >> n >> k;
for (int i = 0; i < n; ++i)
for (int j = 0; j < n; ++j) {
cin >> val[id(i, j)], sum += val[id(i, j)];
if (i) dm[tot++] = {id(i - 1, j), id(i, j)};
if (j) dm[tot++] = {id(i, j - 1), id(i, j)};
}
if (tot > M)
nth_element(dm, dm + M, dm + tot,
[](auto x, auto y) { return getval(x) > getval(y); }),
tot = M;
d = min(tot >> 1, W);
for (int i = 0; i < d; ++i) {
f[1 << i] = getval(dm[i]);
for (int j = 0; j < i; ++j)
if (issim(dm[i], dm[j])) f[(1 << i) | (1 << j)] = INF;
}
for (int i = 0; i < d; ++i)
for (int s = 0; s < (1u << d); ++s)
if ((s & (1 << i)) && cnt(s) <= k) f[s] = iadd(f[s], f[s ^ (1 << i)]);
for (int i = 0; i < tot - d; ++i) {
g[pos(1 << i)] = getval(dm[i + d]);
for (int j = 0; j < i; ++j)
if (issim(dm[i + d], dm[j + d])) g[pos((1 << i) | (1 << j))] = INF;
}
for (int i = 0; i < tot - d; ++i) {
gst[i] = (1 << d) - 1;
for (int j = 0; j < d; ++j)
if (issim(dm[i + d], dm[j])) gst[i] ^= 1 << j;
}
for (int i = 0; i < tot - d; ++i)
for (int p = 0; p < ntot && (num[p] < (1u << (tot - d))); ++p)
if (int s = num[p]; (s & (1 << i)) && cnt(s) <= k)
g[p] = iadd(g[p], g[pos(s ^ (1 << i))]);
for (int p = 0; p < ntot && (num[p] < (1u << (tot - d))); ++p)
if (int s = num[p]; g[p] != INF && cnt(s) <= k) {
int gsts = (1 << d) - 1;
for (int i = 0; i < (tot - d); ++i)
if (s & (1 << i)) gsts &= gst[i];
gval[gsts][cnt(s)] = max(gval[gsts][cnt(s)], g[p]);
}
for (int i = 0; i < d; ++i)
for (int s = 0; s < (1u << d); ++s)
if (s & (1 << i))
for (int j = 0; j <= k; ++j) {
gval[s ^ (1 << i)][j] = max(gval[s ^ (1 << i)][j], gval[s][j]);
}
for (int s = 0; s < (1u << d); ++s)
if (f[s] != INF && cnt(s) <= k) ans = max(ans, gval[s][k - cnt(s)] + f[s]);
cout << sum - ans << endl;
return 0;
}
以上。