【UNR #4】序列妙妙值 分块+DP
只会 80pts.
最裸的暴力(40pts)
令 $f[i][j]$ 表示当前 DP 到 $i$,划分成了 $j$ 段的最小值.
时间复杂度 $O(n^2)$
一点优化(60 ~ 80pts)
有几个测点 $a[i]$ 很小,那么可以直接开一个桶 $s[i][j]$ 表示前缀异或和为 $i$,且划分 $j$ 段的最小值.
修改复杂度:$O(1)$,查询复杂度 $O(v)$,总复杂度 $O(nv)$.
还可以在 $trie$ 树上乱搞,不知道能拿多少分.
正解
分块.
我们发现 $60$ 分解法中查询和修改复杂度差异很大,所以考虑用分块去平衡上述复杂度.
由于数字最大是 $2^{16}$,所以考虑维护 $mi[x][y]$ 表示一个数的前 $8$ 位是 $x$,去匹配一个后 $8$ 位为 $y$ 的贡献.
那么这个修改起来的话只需要枚举后面的 $y$,复杂度为 $O(\sqrt v)$.
查询的话后面是固定的,然后枚举前面的 $x$,复杂度为 $O(\sqrt v)$.
总复杂度就是 $O(nK \sqrt v)$ 的.
代码:
#include <cstdio> #include <cstring> #include <algorithm> #define N 60009 #define ll long long #define setIO(s) freopen(s".in","r",stdin) using namespace std; const int inf=1000000000; int n,K; int a[N],f[N],tmp[N],mi[300][300]; int main() { // setIO("input"); scanf("%d%d",&n,&K); for(int i=1;i<=n;++i) { scanf("%d",&a[i]); a[i]^=a[i-1]; } f[0]=0; for(int i=1;i<=n;++i) { f[i]=inf; } int B=(1<<8)-1; for(int i=1;i<=K;++i) { for(int x=0;x<256;++x) { for(int y=0;y<256;++y) mi[x][y]=inf; } for(int j=0;j<=n;++j) { tmp[j]=inf; for(int x=0;x<256;++x) { // 前面固定,后面是猜的 int det=mi[x][a[j]&B]+((a[j]>>8^x)<<8); tmp[j]=min(tmp[j],det); } for(int x=0;x<256;++x) { int cur=mi[a[j]>>8][x]; int det=f[j]+((a[j]&255)^x); mi[a[j]>>8][x]=min(mi[a[j]>>8][x],det); } } for(int j=0;j<=n;++j) f[j]=tmp[j]; } for(int i=K;i<n;++i) printf("%d ",f[i]); printf("%d",f[n]); printf("\n"); return 0; }