mod 2 - 2025“钉耙编程”中国大学生算法设计暑期联赛(1)T8 题解

前言

题目链接:HDU

题意简述

对于一个数组 \(b\),定义:

  • \(\operatorname{odd}(b)\):先筛选出 \(b\) 中出现次数为奇数的元素,再将这些元素按照“出现次数降序,出现次数相同时按元素大小升序排序”的新数组。
  • \(\operatorname{even}(b)\):先筛选出 \(b\) 中出现次数为偶数的元素,再将这些元素按照“出现次数降序,出现次数相同时按元素大小升序排序”的新数组。

比如 \(b = [1, 2, 1]\),那么 \(\text{odd}(b) = [2]\)
\(b = [3, 3, 2, 2, 1, 2, 4]\),那么 \(\text{even}(b) = [2, 1, 3]\)

现在给你一个长度为 \(n\) 的数组 \(a\),有 \(q\) 次询问,每次询问给你四个数 \(L, R, k, V\)。你需要回答:你能构造出多少种长度为 \(k\),值域为 \([1, V]\) 的数组 \(b\),使得 \(\operatorname{odd}(a[L, R]) = \operatorname{even}(b)\)?答案对 \(2\) 取模。强制在线

\(n,q\leq10^5\)\(a_i\leq10^9\)\(k,V\leq10^{18}\)

题目分析

先不考虑其他所有限制,考虑最终 \(b\) 中有 \(c_i\)\(d_i\),且 \(\sum_{i=1}^mc_i=k\),那么方案数为:

\[C=\binom{k}{c_1}\binom{k-c_1}{c_2}\binom{k-c_1-c2}{c_3}\cdots\binom{k-\sum_{i=1}^{m-1}c_i}{c_m} \]

由于答案对 \(2\) 取模,我们只需要考虑 \(C\equiv1\pmod2\) 的情况,那么对于每一个 \(i\)\(\binom{k-\sum_{j=1}^{i-1}c_j}{c_i}\equiv1\pmod2\)

模数很小,考虑 Lucas 定理,\(\binom{n}{m}\equiv1\pmod2\) 成立当且仅当 \(\operatorname{bits}(m)\subseteq\operatorname{bits}(n)\)\(\operatorname{bits}(x)\) 表示 \(x\) 在二进制下为 \(1\) 的位。

那么对于计算 \(C\) 的过程,可以看做从 \(\operatorname{bits}(k)\) 中不断拿出一些 \(1\)。由于 \(|\operatorname{bits}(k)|\leq60\),每个 \(c_i\) 至少拿一个 \(1\),故 \(m\leq60\)

发现,上述过程是关于 \(b\) 的,而我们其实关心 \(\operatorname{even}(b)\)。事实上,若 \(k\) 为偶数,那么 \(c_i\) 全为偶数,全都出现在了 \(\operatorname{even}(b)\) 中;否则 \(k\) 为奇数,有且仅有一个 \(c_i\) 是奇数。这个特殊情况在后文进一步说明,不妨暂时仅考虑前一种情况。

由于 \(m\leq60\),所以求出 \(\operatorname{odd}(a[L,R])\) 成为可能。考虑建出主席树,主席树上用异或哈希,这样就能在主席树上二分,找出 \(\operatorname{odd}(a[L,R])\)。如果 \(|\operatorname{odd}(a[L,R])|>60\),答案为 \(0\)

考虑让 \(\operatorname{odd}(a[L,R])=\operatorname{even}(b)\)。由于 \(c_i\) 互异(二进制位是不能重复取的),所以如果确定了 \(\{c_m\}\),就不需要考虑出现次数相同,只需要为每一个 \(c_i\) 配对一个值 \(d_i\),就能确定出 \(\operatorname{even}(b)\)。而 \(\operatorname{odd}(a[L,R])\) 无论怎样是确定的,\(m\) 就是确定的,所以对于 \(\operatorname{even}(b)\),我们先按照 \(c_i\) 排序,\(d_i\) 只需要和 \(\operatorname{odd}(a[L,R])\) 相应位置相同即可。换句话说,只要确定了 \(\{c_m\}\),方案就是唯一确定的。

我们还没有考虑 \([1,V]\) 的限制。由于我们上述填值的过程是和 \(\operatorname{odd}(a[L,R])\) 相应位置相同,所以如果 \(\operatorname{odd}(a[L,R])\) 有一个值大于 \(V\),就是无解,否则不需要考虑值域的问题。

问题变成了,确定了 \(k,m\),问 \(\{c_m\}\) 的方案数,这个可以预处理 DP。设 \(f_{i,j,k}\) 表示 \(k\) 一共有 \(i\) 个二进制位,取了 \(j\) 次,共取出 \(k\) 个二进制位的方案数。那么答案就是 \(f_{|\operatorname{bits}(k)|,m,|\operatorname{bits}(k)|}\)

\(f_{i,j,k}\) 的计算先放一放,考虑 \(k\) 为奇数的情况。我们不妨枚举这个 \(c_i\) 占有 \(j+1\) 个 bit 位,其中一位是 \(2^0\),选 bit 位的方案数为 \(\binom{|\operatorname{bits}(k)|-1}{j}\),剩下 \(c_i\) 符合 \(k\) 为偶数的情况,方案数为 \(f_{|\operatorname{bits}(k)|-j-1,m,|\operatorname{bits}(k)|-j-1}\)。考虑与这个特殊的 \(c_i\) 配对的 \(d_i\) 的方案数,就是从 \([1,V]\) 中,除了 \(\operatorname{odd}(a[L,R])\) 中的数任取,方案数为 \(V-m\)

现在考虑如何预处理 \(f_{s,i,j}\)。显然有 \(f_{s,0,0}=1\)。枚举在 \(i-1\) 的基础上多选了 \(k\) 个位,转移到 \(f_{s,i,j+k}\)。贡献是 \(\binom{s-j}{k}f_{s,i-1,j}\) 吗?由于我们不关心 \(c_i\) 的排列,所以如果用 \(\binom{s-j}{k}\),最终答案会多 \(m!\) 的系数。我们每次只需要强制新的 \(c_i\) 必选最低的,没有被选出的 bit 位就能做到有顺序。即 \(f_{s,i,j+k}\gets\binom{s-j-1}{k-1}f_{s,i-1,j}\)

问题解决了!时间复杂度 \(\mathcal{O}(n\log n+\log^4 k)\sim\mathcal{O}(q\log n\log k)\)

代码

#include <cstdio>
#include <iostream>
#include <cassert>
#include <vector>
#include <algorithm>
using namespace std;

using ll = long long;
using ull = unsigned long long;

const int N = 2e5 + 10;
const int V = 65;

ull rd[N];  // 异或 hash 的随机权

inline ull O(ull x) {
	x ^= 45151, x ^= x >> 2, x ^= x << 4;
	return x * x + (651 ^ x) + x * x * 13132;
}

inline ull F(ull x) {
	x *= 1315615ll, x ^= 545451356, x ^= x << 2;
	return O(x * 321) * 315615 + O(O(O(x * x + 6541))) * 56141;
}

inline int C(ll n, ll m) {
    if (n < 0 || m < 0) return 0;
    return (n | m) == n;
}

struct {
    int ls, rs;
    ull s;
} tr[N * 20];
int tot;
void upd(int x, int &y, int l, int r, int v) {
    tr[y = ++tot] = tr[x];
    tr[y].s ^= rd[v];
    if (l == r) return;
    int m = (l + r) >> 1;
    if (v <= m) upd(tr[x].ls, tr[y].ls, l, m, v);
    else upd(tr[x].rs, tr[y].rs, m + 1, r, v);
}
int qry(int x, int y, int l, int r, int L) {
    if (r < L || tr[x].s == tr[y].s) return 0;
    if (l == r) return l;
    int m = (l + r) >> 1;
    int o = qry(tr[x].ls, tr[y].ls, l, m, L);
    return o ? o : qry(tr[x].rs, tr[y].rs, m + 1, r, L);
}

int f[V][V][V];

int n, a[N], b[N], q;
int rt[N];

void solve() {
    scanf("%d", &n);
    for (int i = 1; i <= n; ++i) {
        scanf("%d", &a[i]);
        b[i] = a[i];
    }
    sort(b + 1, b + n + 1);
    int V = unique(b + 1, b + n + 1) - b - 1;
    tot = 0;
    for (int i = 1; i <= n; ++i) {
        a[i] = lower_bound(b + 1, b + V + 1, a[i]) - b;
        upd(rt[i - 1], rt[i], 1, V, a[i]);
    }
    scanf("%d", &q);
    for (int lst = 0, l, r; q--; ) {
        ll k, v;
        scanf("%d%d%lld%lld", &l, &r, &k, &v);
        l ^= lst, r ^= lst, k ^= lst, v ^= lst;
        int cnt = 0;
        bool ok = true;
        for (int i = 1; i <= V; ) {
            int g = qry(rt[l - 1], rt[r], 1, V, i);
            if (g == 0) {
                break;
            }
            if (b[g] > v) {
                ok = false;
                break;
            }
            if (++cnt > 60) {
                ok = false;
                break;
            }
            i = g + 1;
        }
        if (!ok) {
            puts("0");
            continue;
        }
        int ans = 0;
        int c = __builtin_popcountll(k);
        if (k & 1) {
            --c;
            for (int i = 0; i <= c; ++i)
                ans ^= f[i][cnt][i] * C(c, i);
            if ((v - cnt) % 2 == 0) {
                ans = 0;
            }
        } else {
            ans = f[c][cnt][c];
        }
        printf("%d\n", ans);
        lst += ans;
    }
}

int main() {
    for (int i = 0; i < N; ++i)
        rd[i] = F(rd[i - 1] ^ i);
    for (int s = 0; s < V; ++s) {
        f[s][0][0] = 1;
        for (int i = 1; i <= s; ++i)
            for (int j = 0; j <= s; ++j)
                for (int k = 1; j + k <= s; ++k)
                    f[s][i][j + k] ^= f[s][i - 1][j] * C(s - j - 1, k - 1);
    }
    int t;
    scanf("%d", &t);
    while (t--)
        solve();
    return 0;
}
posted @ 2025-07-19 09:39  XuYueming  阅读(71)  评论(2)    收藏  举报