题解:CF1946D Birthday Gift

· · 题解

来写个较其它题解更玄学一点的做法。

题目描述

给定两个整数 n,x 和长度为 n 的序列 a,求出最大的 k 使得整个序列 a 可以被分成相邻的 k 个区间 [l_1,r_1],[l_2,r_2],[l_3,r_3],\ldots,[l_k,r_k],满足:

解题思路

首先形如 (a_l\oplus a_{l+1}\oplus \ldots\oplus a_r) 的表达式让人不由得联想到前缀异或和,即设

s_i=a_1\oplus a_2\oplus \ldots\oplus a_i

特别地,令 s_0=0。则根据异或运算的特性,有

\begin{aligned} a_l\oplus a_{l+1}\oplus \ldots\oplus a_r&=(a_1\oplus a_1)\oplus(a_2\oplus a_2)\oplus\ldots\oplus(a_l\oplus a_{l+1}\oplus \ldots\oplus a_r) \\ &=(a_1\oplus a_2\oplus \ldots\oplus a_{l-1})\oplus(a_1\oplus a_2\oplus \ldots\oplus a_r) \\ &=s_{l-1}\oplus s_r \end{aligned}

于是题目中最后一个条件变为:

(s_{l_1-1}\oplus s_{r_1})|(s_{l_2-1}\oplus s_{r_2})|(s_{l_3-1}\oplus s_{r_3})|\ldots|(s_{l_k-1}\oplus s_{r_k})\le x

又注意到对于任意 1\le i<k,有 r_i+1=l_{i+1},所以 l_{i+1}-1=r_i。故上式变为:

(0\oplus s_{r_1})|(s_{r_1}\oplus s_{r_2})|(s_{r_2}\oplus s_{r_3})|\ldots|(s_{r_{k-1}}\oplus s_{r_k})\le x

其中 (0\oplus s_{r_1}) 实际上就是 s_{r_1}

引理:对于两个非负整数 a,b,有 a|(a\oplus b)=a|b

证明:

$\qquad$ 对于任意位,若 $a$ 在该位上是 $1$,则 $a|(a\oplus b)$ 在该位的值必然是 $1$。 $\qquad$ 若 $a$ 在该位上是 $0$,则 $b$ 在该位上只有为 $1$,才能使 $a|(a\oplus b)$ 在该位的值也为 $1$。 $\qquad$ 综上,$a|(a\oplus b)$ 在某一位的值是 $1$ 当且仅当 $a$ 和 $b$ 当中至少一个在该位的值也是 $1$,相当于 $a|(a\oplus b)=a|b$,证毕。

使用上述结论,上式可进一步做如下改变:

\begin{aligned} (0\oplus s_{r_1})|(s_{r_1}\oplus s_{r_2})|(s_{r_2}\oplus s_{r_3})|(s_{r_3}\oplus s_{r_4})|\ldots|(s_{r_{k-1}}\oplus s_{r_k})&\le x \\ (s_{r_1}|(s_{r_1}\oplus s_{r_2}))|(s_{r_2}\oplus s_{r_3})|(s_{r_3}\oplus s_{r_4})|\ldots|(s_{r_{k-1}}\oplus s_{r_k})&\le x \\ s_{r_1}|(s_{r_2}|(s_{r_2}\oplus s_{r_3}))|(s_{r_3}\oplus s_{r_4})|\ldots|(s_{r_{k-1}}\oplus s_{r_k})&\le x \\ s_{r_1}|s_{r_2}|(s_{r_3}|(s_{r_3}\oplus s_{r_4}))|\ldots|(s_{r_{k-1}}\oplus s_{r_k})&\le x\end{aligned}

最终得出 s_{r_1}|s_{r_2}|s_{r_3}|\ldots|s_{r_k}\le x,限制为 1\le r_1<r_2<r_3<\cdots<r_k=n

现在考虑贪心的取数过程。假设初始时所有 n 个数字(s_1,s_2,s_3,\ldots,s_n)都选,然后拆二进制位考虑(从高到低),根据 x 的限制逐步删除那些不合法的数字。假定目前选的所有数字在第 i 位上的按位与结果为 ax 的第 i 位是 b。分类讨论:

这样答案就能得出来了。

参考代码

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;

const int MAXN = 1e5 + 10;

int t, n, x, s[MAXN];

bool no[MAXN];

int main()
{
    scanf("%d", &t);
    while (t--)
    {
        scanf("%d%d", &n, &x);
        for (int i = 1; i <= n; i++)
            scanf("%d", s + i), s[i] ^= s[i - 1];
        memset(no, 0, sizeof(bool) * (n + 5));
        int ans = -1, res = n;
        for (int i = 29; i >= 0; i--)
        {
            bool i1 = 0;
            for (int j = 1; j <= n; j++)
                if (!no[j]) i1 |= ((s[j] >> i) & 1);
            if (!i1 && ((x >> i) & 1)) {ans = max(ans, res); break;}
            if (i1 && ((x >> i) & 1) && !((s[n] >> i) & 1))
            {
                int res1 = res;
                for (int j = 1; j <= n; j++)
                    if ((s[j] >> i) & 1) res1 -= !no[j];
                ans = max(ans, res1);
            }
            if (i1 && !((x >> i) & 1))
            {
                if ((s[n] >> i) & 1) break;
                for (int j = 1; j <= n; j++)
                    if ((s[j] >> i) & 1) res -= !no[j], no[j] = 1;
            }
            if (!i) ans = max(ans, res);
        }
        printf("%d\n", ans);
    }

    return 0;
}