Loading

P10200 [湖北省选模拟 2024] 花神诞日 / sabzeruz 解题报告

是这样的,被模拟赛 T4 单杀了。

image

这个 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\) 时,有

\[f(i, j) = f(i - 1, j) [S_i \oplus S_{i - 1} \ge k_1] \]

\(j + 1 = i\) 时,有

\[f(i, j) = \sum_{j' + 1 < i} g(i - 1, j') [S_{j'} \oplus S_{i} \ge k_1] \]

然后发现第一个转移的系数 \(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;
}
posted @ 2026-08-25 21:54  Trent900  阅读(17)  评论(0)    收藏  举报