📜  门|门 CS 1999 |第 51 题(1)

📅  最后修改于: 2023-12-03 15:12:46.106000             🧑  作者: Mango

题目介绍

本题为 "门|门 CS 1999" 系列中的第 51 题,题目难度为普及/提高-。

题目描述

给定一个长度为 $n$ 的正整数序列 $a$ 和一个正整数 $k$,请你求出定长为 $k$ 的连续子序列,使得这 $k$ 个数的异或和最大。

输入格式

第一行是两个正整数 $n$ 和 $k$,表示序列 $a$ 的长度和所求子序列的长度。

第二行包含 $n$ 个正整数,表示序列 $a$。

输出格式

共一行,包含一个正整数,表示定长连续子序列的最大异或和。

样例输入
5 2
5 2 7 8 6
样例输出
15
数据范围

$1\leqslant n\leqslant 10^5,1\leqslant k\leqslant 100,\sum\limits_{i=1}^nk\leqslant 10^5,0\leqslant a_i<2^{32}$

解题思路

首先我们考虑朴素做法,用双重循环枚举所有的定长连续子序列,然后求出它们的异或和,取最大值即可。复杂度为 $O(nk^2)$,显然不能通过本题。

我们发现,对于一个连续子序列 $[l,r]$,它的异或和为:

$S_{l,r}=a_l\oplus a_{l+1}\oplus\cdots\oplus a_r$

如果我们已经求出了 $[l+1,r]$、$[l,r-1]$ 的异或和,可以得到:

$S_{l,r}=a_l\oplus S_{l+1,r}=S_{l,r-1}\oplus a_r$

基于异或的性质,我们可以 $O(1)$ 完成一个子区间的查询,这启示我们可以用 dp 求解本题。

假设 $f_{i,j}$ 为前 $i$ 个元素,长度为 $j$ 的连续子序列的最大异或和,那么转移方程为:

$f_{i,j}=\max{f_{i-1,j},f_{i-1,j-1}\oplus a_i}$

其中,$f_{i-1,j}$ 表示不选择当前元素 $a_i$,$f_{i-1,j-1}\oplus a_i$ 表示选择当前元素 $a_i$。

最终答案即为 $\max\limits_{i=k}^n f_{i,k}$。

时间复杂度 $O(nk)$。

参考代码

#include <bits/stdc++.h>

using namespace std;

typedef unsigned int uint;

const int N = 1e5 + 10, K = 110;

int n, k;
uint a[N];
uint f[N][K];

uint calc(uint x) {
    for (int i = 31; ~i; i--) {
        if (x >> i & 1) {
            return x ^ ((1 << i) - 1);
        }
    }
    return 0;
}

int main() {
    cin >> n >> k;
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
        a[i] = calc(a[i]);
    }

    for (int i = 1; i <= n; i++) {
        for (int j = k; j; j--) {
            f[i][j] = max(f[i-1][j], f[i-1][j-1] ^ a[i]);
        }
    }

    uint ans = 0;
    for (int i = k; i <= n; i++) {
        ans = max(ans, f[i][k]);
    }
    cout << ans << endl;

    return 0;
}
算法标签
  • DP
  • 异或运算