[AGC071A] XOR Cross Over 做题笔记
VP了这场AGC,得到了零分......
题意简述
解法
观察性质。
对于一个数列 \(\{a_l\oplus B, a_{l+1}\oplus B, ......, a_r\oplus B\}\),如果将其分为 \([l, m]\) 和 \([m+1, r]\),设 \([l, m]\) 那个数列是: \(\{a_l\oplus C, a_{l+1}\oplus C, ......, a_m\oplus C\}\)
那么有
一个结论是某一时刻产生了一个数列 \(\{a_l\oplus B, a_{l+1}\oplus B, ......, a_r\oplus B\}\)。
那么一定存在 \(a, b\),满足 \(B=\bigoplus_{i=a}^{l-1}a_i\oplus\bigoplus_{i=r+1}^{b}a_i\),证明显然。
利用这个方法可以设计 \(O(n^5)\) 的 DP,定义是 \(F_{a, l, r, b}\) 表示对数列 \(\{a_l\oplus B, a_{l+1}\oplus B, ......, a_r\oplus B\}\)(\(B\) 的含义如上)做操作后得到的数字和的最小值。
考虑这个 DP 慢的原因在于需要将 \(O(n^2)\) 的状态用于表述 \(B\) 的值,但事实上,\(B\) 的值仅仅在 \(l=r\)(即统计贡献时)用到,所以考虑更换贡献统计方法。
为此,我们愿意建出这颗分裂树(是一颗二叉树,每个节点代表一个区间,儿子表示他分裂出的两个区间)。考虑某个叶节点最终会是哪些数的异或和,发现可以从一个叶节点 \(u\) 出发,不断移动到其父亲,直到它有一个长度为奇数的兄弟,设此时这个节点的父亲 \(p\) 为区间 \([l, r]\),那么 \(u\) 最终的值是 \(\bigoplus_{i=l}^ra_i\)。
知道这一点后,考虑在 \(p\) 处统计 \(u\) 的贡献,便能省去记录 \(a, b\) 这两维度。
设 \(f_{i, j}\) 表示通过最优的划分方案,\([i, j]\) 这一分裂树上节点的子树的各点贡献和的最大值。
注意到一个长度为偶数的区间,它的子树内所有叶节点的贡献都一定在这个子树内被统计到,对于奇数区间,一定恰有一个叶节点的贡献没有在这个子树内被统计到。(数学归纳法不难证明)
于是有
直接转移即可。需要注意的一点是如果 \(n\) 是奇数,有一个点没有被统计到,而这个点的贡献一定是所有数的异或和,把他加上即可。
总结
第一步的性质是显然的。关键是 DP 的优化。
在初步的 DP 中,\(i, j\) 显然是重要的,那么为了消解掉不太重要的 \(a, b\),必须更换统计贡献的方式,因此考虑最终的每个数由哪些数异或而来。实际上,哪怕不从优化 DP 的角度,考虑这一点也是自然的。
但是仅仅得出这一不结论依然无法起到优化效果。所以这道题提供了一个 DP 优化的技巧:转移贡献统计位置。通过这样,就能设计出优秀的 DP 状态及其转移。
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define rep(i, a, b) for(ll i=a;i<=b;i++)
const ll N=509, INF=1e18;
ll f[N][N], n, a[N], yih[N];
int main(){
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
cin >> n;
rep(i, 1, n)cin >> a[i];
rep(i, 1, n)yih[i]=yih[i-1]^a[i];
rep(len, 2, n){
rep(i, 1, n-len+1){ll j=i+len-1;
f[i][j]=INF;
if((j-i+1)&1)
rep(k, i, j-1)f[i][j]=min(f[i][j], f[i][k]+f[k+1][j]);
else
rep(k, i, j-1)f[i][j]=min(f[i][j], f[i][k]+f[k+1][j]+((k-i+1)%2==1?2*(yih[j]^yih[i-1]):0));
}
}
cout << f[1][n]+((n&1)?yih[n]:0);
return 0;
}