发掘性质:假设一个区间被分裂成两个区间,其中一个长为奇数,则在这之前异或的值 X 对于另一个区间完全没有影响。
显然奇数区间异或和会带一个 X,再异或另一个区间就抵消了。
假设已经知道分裂树,那么底层一个点的权值相当于不断往上走,直到遇到一个点的兄弟为奇数,它的权值就是这个点的父亲代表的区间异或和。
考虑 dp。设 fi,j 表示区间 [i,j] 的答案,我们需要分讨如何分裂这个区间。
假设分成 [i,k] 和 [k+1,j] 两个区间。
- 当两个区间都为奇数,由于奇数一定会分裂为偶数和奇数,所以到最后一定有两个点(分属两个区间)在跳分裂树的时候会跳到当前区间并计算贡献,所以 fi,j=fi,k+fk+1,j+2×sumi,j,其中 sumi,j 表示 [i,j] 的异或和。
- 当两个区间不都为奇数,则必有一个为偶数。对于偶数一定会在之后的操作中分裂为两个奇数(因为最后要分为 n 个长度为 1 的小区间),因此不用额外计算贡献。对于奇数,当前还没有到计算贡献的时候(没有遇到奇数兄弟),所以要再往上合并才会计算最终贡献,则有 fi,j=fi,k+fk+1,j。
注意最后需要判定 [1,n] 是否长度为奇数,因为要计算多余那个点的贡献。
#include <bits/stdc++.h>
using namespace std;
const int N = 505;
const long long INF = 1e18;
int n;
long long f[N][N], a[N], s[N];
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++) scanf("%lld", &a[i]), s[i] = s[i - 1] ^ a[i];
for (int len = 2; len <= n; len++)
for (int i = 1; i + len - 1 <= n; i++) {
int j = i + len - 1;
f[i][j] = INF;
for (int k = i; k < j; k++) {
int l = k - i + 1, r = j - k;
if (l & r & 1) f[i][j] = min(f[i][j], f[i][k] + f[k + 1][j] + ((s[j] ^ s[i - 1]) << 1) );
else f[i][j] = min(f[i][j], f[i][k] + f[k + 1][j]);
}
}
printf("%lld\n", f[1][n] + (n & 1) * s[n]);
return 0;
}