题解:uoj549 序列妙妙值
题意:很简单了,不再赘述。
做法:
发现 \(k=8\),猜想做法是 \(O(nk ?)\) 的,但是显然 \(?=V\) 太大了,我们考虑优化。
首先我们先写出来一个 dp,\(dp_{i,j} = \min_{k=1}^{i-1} dp_{k,j-1}+s_i\oplus s_k\),\(s_i\) 代表序列的前缀异或和。
那么我们考虑对 \(k\) 开个桶记录目前最小的答案,直接转是 \(O(nkV)\) 的,过不了。
我们发现一件事情,我们有两种进行处理的方式:
-
枚举其他集合 \(t\),提前计算 \(s_k\oplus t\) 的贡献。
-
扫到当前集合的时候去枚举其他集合计算答案。
这两种方案都是 \(O(V)-O(1)\) 的,我们能否均摊一下呢?
答案是可以的,我们考虑对于前一半的位使用第一种计算方式,后一半的位使用后一种计算方式,然后就可以做到 \(O(nk\sqrt V)\) 了。
代码:
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int maxn = 60005, V = (1 << 8);
int dp[maxn][10], res[10][V][V];
int n, k, a[maxn];
int pre(int x) {
return (x & (V - 1));
}
int suf(int x) {
return ((x >> 8) & (V - 1));
}
signed main() {
cin >> n >> k;
for (int i = 1; i <= n; i++)
cin >> a[i], a[i] ^= a[i - 1];
memset(dp, 0x3f, sizeof(dp));
memset(res, 0x3f, sizeof(res));
dp[0][0] = 0;
for (int i = 0; i < V; i++)
res[0][i][0] = min(res[0][i][0], i);
for (int i = 1; i <= n; i++) {
int x = pre(a[i]), y = suf(a[i]);
for (int j = 1; j <= k; j++) {
for (int s = 0; s < V; s++)
dp[i][j] = min(dp[i][j], ((s ^ y) << 8) + res[j - 1][x][s]);
}
for (int j = 1; j < k; j++) {
for (int s = 0; s < V; s++)
res[j][s][y] = min(res[j][s][y], dp[i][j] + (x ^ s));
}
if(i >= k)
cout << dp[i][k] << " ";
}
return 0;
}

浙公网安备 33010602011771号