peiwenjun's blog 没有知识的荒原

UOJ549 【UNR #4】序列妙妙值 题解

题目描述

定义一个长为 \(n\) 的序列的妙妙值为,将序列划分成 \(k\) 段,每一段异或和之和的最小值。

给定序列 \(a\) ,对每个长度 \(\ge k\) 的前缀,求其序列妙妙值。

数据范围

  • \(1\le k\le n\le 60000\)
  • \(1\le k\le 8,0\le a_i\lt 2^{16}\)

时间限制 \(\texttt{2s}\) ,空间限制 \(\texttt{512MB}\)

分析

\(s_j=\bigoplus\limits_{j=1}^ia_j\) ,令 \(f_{i,j}\) 表示长为 \(i\) 的前缀划分成 \(j\) 段的最小代价。

转移方程:

\[f_{i,j}=\min_{0\le k<i}f_{k,j-1}+s_i\oplus s_k \]

时间复杂度 \(\mathcal O(n^2k)\) ,期望得分 \(40pts\)


发现有 \(a_i\le 2^8\) 的部分分,这启示我们对值域开桶。

\(f_{k,j-1}\) 放到编号为 \(s_i\) 的桶中,转移时 \(\mathcal O(V)\) 枚举所有桶,时间复杂度 \(O(nkV)\) ,期望得分 \(60pts\)


状态数已经没法优化了,还是要从转移入手。

发现更新桶速度很快,只需要 \(\mathcal O(1)\) ;但是查询很慢,需要 \(\mathcal O(V)\)

考虑用分块平衡修改和查询的时间复杂度

对前 \(8\) 位开桶, \(g_{x,y}\) 表示桶中存的数前 \(8\) 位为 \(x\) ,需要异或一个后 \(8\) 位为 \(y\) 的数时的最小代价。

  • 用桶计算 \(dp\) 值:枚举 \(x\) 即可确定 \(s\oplus x\) 的前 \(8\) 位的贡献,用 g[x][s&255]+((x^(s>>8))<<8) 更新答案。
  • \(dp\) 值更新桶:枚举此后询问的数后 \(8\) 位为 \(y\) ,由于需要算上 s^y 的后 \(8\) 位的贡献,因此用 f[i][j]+(y^(s&255)) 更新 g[s>>8][y] 即可。

时间复杂度 \(\mathcal O(nk\sqrt V)\)

#include<bits/stdc++.h>
using namespace std;
const int maxn=6e4+5;
int k,n;
int s[maxn];
int f[9][maxn],g[256][256];
void chmin(int &x,int y)
{
    x=min(x,y);
}
int main()
{
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++) scanf("%d",&s[i]),s[i]^=s[i-1];
    memset(f,0x3f,sizeof(f));
    f[0][0]=0;
    for(int j=1;j<=k;j++)
    {
        memset(g,0x3f,sizeof(g));
        for(int i=1;i<=n;i++)
        {
            for(int y=0;y<256;y++) chmin(g[s[i-1]>>8][y],f[j-1][i-1]+(y^(s[i-1]&255)));
            for(int x=0;x<256;x++) chmin(f[j][i],g[x][s[i]&255]+((x^(s[i]>>8))<<8));
        }
    }
    for(int i=k;i<=n;i++) printf("%d ",f[k][i]);
    putchar('\n');
    return 0;
}

posted on 2022-08-17 20:34  peiwenjun  阅读(89)  评论(0)    收藏  举报

导航