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\),那么方案数为:
由于答案对 \(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;
}
本文作者:XuYueming,转载请注明原文链接:https://www.cnblogs.com/XuYueming/p/18992497。
若未作特殊说明,本作品采用 知识共享署名-非商业性使用 4.0 国际许可协议 进行许可。

浙公网安备 33010602011771号