题意:给一个长度为nnn的序列aaa,将其分成kkk段,不能为空,求所有段的异或和之和的最小值。
n≤6×104,ai<216,k≤8n\leq 6\times 10^4,a_i <2^{16},k\leq 8n≤6×104,ai<216,k≤8
先求个前缀异或和,显然有个 dp:
f(n,k)=mini=k−1n−1{f(i,k−1)+(an⊕ai)}f(n,k)=\min_{i=k-1}^{n-1}\{f(i,k-1)+(a_n\oplus a_i)\}f(n,k)=i=k−1minn−1{f(i,k−1)+(an⊕ai)}
发现值域很小,可以在跑的时候顺便维护前面的每个ai=va_i=vai=v的iii中f(i,k−1)f(i,k-1)f(i,k−1)的最小值,复杂度O(nVk)O(nVk)O(nVk),就能拿到 60 分的好成绩。
考虑优化。这个算法是询问O(V)O(V)O(V),修改O(1)O(1)O(1)的,在修改的时候暴力预处理所有最小值可以做到询问O(1)O(1)O(1),修改O(V)O(V)O(V),不难想到根号分治。
修改的时候暴力后888位,查询的时候暴力前888位,就可以做到O(V)O(\sqrt V)O(V)
总复杂度O(knV)O(kn\sqrt V)O(knV)
我好菜啊……
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cctype>
#define MAXN (1<<16)+5
#define MAXM (1<<8)+5
using namespace std;
int a[MAXN],f[10][MAXN];
int mn[10][MAXM][MAXM];
int main()
{int n,k;scanf("%d%d",&n,&k);for (int i=1;i<=n;i++) scanf("%d",&a[i]),a[i]^=a[i-1];memset(mn,0x3f,sizeof(mn));memset(f,0x3f,sizeof(f));for (int i=0;i<(1<<8);i++) mn[0][0][i]=i;for (int T=1;T<=k;T++)for (int i=0;i<=n;i++){if (i>=T)for (int S=0;S<(1<<8);S++) f[T][i]=min(f[T][i],mn[T-1][S][a[i]&255]+(((a[i]>>8)^S)<<8));for (int S=0;S<(1<<8);S++)mn[T-1][a[i]>>8][S]=min(mn[T-1][a[i]>>8][S],f[T-1][i]+((a[i]&255)^S));} for (int i=k;i<=n;i++) printf("%d ",f[k][i]);return 0;
}