题解: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)\) 的,过不了。

我们发现一件事情,我们有两种进行处理的方式:

  1. 枚举其他集合 \(t\),提前计算 \(s_k\oplus t\) 的贡献。

  2. 扫到当前集合的时候去枚举其他集合计算答案。

这两种方案都是 \(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;
}
posted @ 2025-07-25 16:00  LUlululu1616  阅读(42)  评论(0)    收藏  举报