P10200 [湖北省选模拟 2024] 花神诞日 / sabzeruz 解题报告
是这样的,被模拟赛 T4 单杀了。

这个 OP 好燃,我要听我要听。但是搜不到 Ghost Sequence,只能再等一个月了。
题意是这样的:将一个数组 \(S\) 通过黑白染色划分成两个字序列 \(S_1\),\(S_2\),使得 \(f(S_1) > k_1\),\(f(S_2) > k_2\),求染色方案数。其中 \(f(T) = \min_{i \neq j} (T_i \oplus T_j)\)。
我会爆搜!
观察部分分,我们发现可以直接爆搜,能拿到 \(4\) 分。
我会建图!
考虑特殊性质 A:对于 \(S\) 中任意两个元素,若 \(S_i \oplus S_j < k_1\),那么我们连边。显然,一条边两端颜色不能一样。那么直接判二分图就好了。如果存在奇环,答案即为 \(0\)。这样总共可以拿到 \(12\) 分。
我会套路!
现在我们走投无路了,利用一下异或运算吧。考虑一个集合,两个数异或的最小值怎么快速算出来。如果你做过的题够多,你就知道其实 \(f(S)\) 等于将 \(S\) 排序后的 \(\min_{1 \le i < |S|} (S_i \oplus S_{i + 1})\)。想要证明其实非常容易,考虑在字典树上观察这个序列:想要异或尽可能小,就要公共前缀尽可能长,因此相邻的元素中一定会包含最优解。
将 \(S\) 提前排序后,我们就把对于集合内每个元素的问题转化为了相邻元素的问题。
我会 DP!
不妨记 \(f(i, j)\) 为考虑前 \(i\) 个元素,元素 \(i\) 被放进了 \(S_1\),上一个异色元素是 \(j\) 的方案数,\(g(i, j)\) 反过来。
考虑一下转移:当 \(j + 1 < i\) 时,有
当 \(j + 1 = i\) 时,有
然后发现第一个转移的系数 \(c = [S_i \oplus S_{i - 1} \ge k_1]\) 其实和 \(j\) 无关。如果 \(c = 0\),就相当于清空数组,否则就是复制一遍。
我会数据结构!
现在考虑第二种转移,我们想要找到所有 \(S_{j'} \oplus S_{i} \ge k_1\) 的 \(g(i - 1, j')\) 之和,就是在一个数组里面找到所有编号异或某一个值 \(\ge k_1\) 的元素的权值之和。
稍加思索一下,我们会发现,这可以通过在字典树上统计子树内叶子结点的权值之和来实现。那么转移一次和插入一个数其实都可以 \(\mathcal{O}(\log n)\) 实现!
因此我们拿两棵 Trie 维护 \(f\),\(g\) 的每一层即可。答案是 \(f(n, *) + g(n, *)\)。
现在可以通过本题。
#include <bits/stdc++.h>
#define int long long
#define Misaka namespace
#define Network std
using Misaka Network;
const int N = 2e5 + 7, MOD = 1e9 + 7, M = 1.2e7 + 7;
int n, k1, k2, s[N];
void add(int &x, int y){
x += y;
if(x >= MOD) x -= MOD;
}
struct Trie{
int nxt[M][2], idx, sum[M];
void insert(int x, int val){
int cur = 0; add(sum[cur], val);
for(int i = 59; i >= 0; i --){
int dir = ((x >> i) & 1);
if(!nxt[cur][dir]) nxt[cur][dir] = ++ idx;
cur = nxt[cur][dir];
add(sum[cur], val);
}
}
int get(int num, int tar){
int cur = 0, res = 0;
for(int i = 59; i >= 0; i --){
int ni = ((num >> i) & 1), ti = ((tar >> i) & 1);
if(ti){
if(!nxt[cur][ni ^ 1]) return res;
cur = nxt[cur][ni ^ 1];
}else{
if(nxt[cur][ni ^ 1]) add(res, sum[nxt[cur][ni ^ 1]]);
if(!nxt[cur][ni]) return res;
cur = nxt[cur][ni];
}
}
add(res, sum[cur]);
return res;
}
void clr(){
nxt[0][0] = nxt[0][1] = 0;
sum[0] = 0;
}
} f, g;
signed main(){
ios::sync_with_stdio(0), cin.tie(0);
cin >> n >> k1 >> k2;
for(int i = 1; i <= n; i ++) cin >> s[i];
sort(s + 1, s + n + 1);
int fe = 1, ge = 1;
for(int i = 2; i <= n; i ++){
int rf = g.get(s[i], k1) + ge;
int rg = f.get(s[i], k2) + fe;
int c1 = ((s[i] ^ s[i - 1]) >= k1);
int c2 = ((s[i] ^ s[i - 1]) >= k2);
if(!c1) f.clr(), fe = 0;
if(!c2) g.clr(), ge = 0;
f.insert(s[i - 1], rf);
g.insert(s[i - 1], rg);
}
cout << (f.sum[0] + g.sum[0]) % MOD << "\n";
return 0;
}

浙公网安备 33010602011771号