题目
第一道题:传送门 to CF。下面就以这道题为例。
第二道题:传送门 to CF。
思路
解决计数问题,先想判定问题。在这道题里,我已经因此吃了大亏了……
如果是判定问题,可以考虑求解 max ( a i ⊕ a j ) \max(a_i\oplus a_j) max(ai⊕aj) 。最常见的方法是,依次插入每个数的同时判断 max \max max 。但显然在这里就不易于扩展了;我们最好找一个不基于动态插入的做法。
其实就是暴力。模拟一次就知道了。从根开始,如果 0 , 1 0,1 0,1 都非空,就得在这两个子树内各选出一个;否则就在某个儿子子树内选出俩。所以可以记 ⟨ x , y ⟩ \langle x,y\rangle ⟨x,y⟩ 表示 x x x 和 y y y 子树内各选出一个是目前的 max \max max 之一(前缀最优)。一层一层转移,如果这一层的所有 ⟨ x , y ⟩ \langle x,y\rangle ⟨x,y⟩ 都不能造出 1 1 1,那就转移到 ⟨ x 0 , y 0 ⟩ , ⟨ x 1 , y 1 ⟩ \langle x_0,y_0\rangle,\langle x_1,y_1\rangle ⟨x0,y0⟩,⟨x1,y1⟩,其中 x d x_d xd 表示 x x x 的 d d d 儿子。否则就转移到 ⟨ x 0 , y 1 ⟩ , ⟨ x 1 , y 0 ⟩ \langle x_0,y_1\rangle,\langle x_1,y_0\rangle ⟨x0,y1⟩,⟨x1,y0⟩ 。
有趣的是,复杂度是 O ( n log V ) \mathcal O(n\log V) O(nlogV) 的,即 t r i e \tt trie trie 的点数。因为每个点最多出现在一个二元组中。
计数问题又怎么做呢?其实很简单,必须 把判定问题时所需的状态记录下来,就像这道题,根本不用怕。因为内层已经几乎是 d p \tt dp dp 了,它只保留了最精简的信息,一般不会再简化了。
所以我们要记录一大堆 ⟨ x , y ⟩ \langle x,y\rangle ⟨x,y⟩ 吗?其实并不用。因为上面的 “一层一层转移” 并非必要。分析复杂度发现,不管这一位是选 0 0 0 还是选 1 1 1,每个点还是只能出现在一个二元组中,复杂度仍然正确。所以我们不必全局考虑,只需暴力递归。但是二元组肯定是基本信息,不能再减。
于是记 f ( x , y ) f(x,y) f(x,y) 为,最大值是 x , y x,y x,y 子树内的两个数异或得到的,有多少种从二者子树中选数的方案。如果 max \max max 的这一位是 0 0 0,那么要么只有 x 0 , y 0 x_0,y_0 x0,y0 存在,要么只有 x 1 , y 1 x_1,y_1 x1,y1 存在,分别递归,然后加起来。
如果 max \max max 的这一位是 1 1 1 呢?就有三种情况了:
- 有 x 0 , y 1 x_0,y_1 x0,y1,而 x 1 , y 0 x_1,y_0 x1,y0 至少有一个是空的。递归 f ( x 0 , y 1 ) f(x_0,y_1) f(x0,y1),乘系数 2 s i z e ( x 1 ) + 2 s i z e ( y 0 ) − 1 2^{size(x_1)}+2^{size(y_0)}-1 2size(x1)+2size(y0)−1 即可。此处 − 1 -1 −1 是因为 ∅ \varnothing ∅ 被算了两次。
- 有 x 1 , y 0 x_1,y_0 x1,y0,而 x 0 , y 1 x_0,y_1 x0,y1 至少有一个是空的。同上。
- 同时有 x 0 , y 1 x_0,y_1 x0,y1 和 x 1 , y 0 x_1,y_0 x1,y0 。此时两边都要递归;怎么合并 max \max max 呢?
不要陷入求 max \max max 的误区!判定问题并不是求 max \max max,而是 max ⩽ x \max\leqslant x max⩽x 。很容易看出,两边其实都 ⩽ x \leqslant x ⩽x 就可以了!所以直接乘法原理相乘即可。
但是我们的计数问题好像会考虑 max \max max 这一位是 0 0 0 和 1 1 1 两种情况,都递归,复杂度就不正确了!所以已经 ⩽ x \leqslant x ⩽x 的时候不能递归,要用 s i z e size size 直接计算。
时间复杂度 O ( n log V ) \mathcal O(n\log V) O(nlogV) 。
代码
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cctype>
using namespace std;
# define rep(i,a,b) for(int i=(a); i<=(b); ++i)
# define drep(i,a,b) for(int i=(a); i>=(b); --i)
typedef long long llong;
inline int readint(){
int a = 0, c = getchar(), f = 1;
for(; !isdigit(c); c=getchar())
if(c == '-') f = -f;
for(; isdigit(c); c=getchar())
a = (a<<3)+(a<<1)+(c^48);
return a*f;
}
const int MOD = 998244353;
inline void modAddUp(int &x,const int y){
if((x += y) >= MOD) x -= MOD;
}
const int MAXN = 150005, LOGX = 30;
int powtwo[MAXN];
namespace Trie{
int ch[MAXN*LOGX][2], cntNode = 1;
int siz[MAXN*LOGX];
void insert(int x){
int o = 1; // avoid 0
for(int i=LOGX-1; ~i; --i){
int &d = ch[o][x>>i&1];
if(!d) d = ++ cntNode;
o = d, ++ siz[o];
}
}
# define _dfs(a,b) dfs(ch[x][a],ch[y][b],bound,d-1)
int dfs(int x,int y,const int bound,int d=LOGX-1){
int res = 0; if(!(~d) || !x || !y) return 0;
if(!(bound>>d&1)){ // this must be zero
res = _dfs(0,0); modAddUp(res,_dfs(1,1));
return res; // no more things to do
}
// if maximum is 0 on this bit
if(x != y) // already forked
rep(v,0,1) res = int((res+(powtwo[siz[ch[y][v]]]-1)
*llong(powtwo[siz[ch[x][v]]]-1))%MOD);
else rep(v,0,1) modAddUp(res,powtwo[siz[ch[x][v]]]-1);
// if maximum is 1 on this bit
if(x == y){
modAddUp(res,_dfs(0,1));
return res; // only way: fork
}
const int lson = _dfs(0,1), rson = _dfs(1,0);
// case 1: generated by <0,1> and <1,0>
res = int((res+llong(lson)*rson)%MOD);
// case 2: generated by only <0,1>
res = int((res+llong(powtwo[siz[ch[x][1]]]
+powtwo[siz[ch[y][0]]]-1)*lson)%MOD);
// case 3: generated by only <1,0>
res = int((res+llong(powtwo[siz[ch[x][0]]]
+powtwo[siz[ch[y][1]]]-1)*rson)%MOD);
return res;
}
}
int main(){
int n = readint(), bound = readint()+1;
rep(i,powtwo[0]=1,n) // pre-compute
powtwo[i] = (powtwo[i-1]<<1)%MOD;
if(bound>>30){
printf("%d\n",powtwo[n]-1);
return 0; // everything can be chosen
}
rep(i,1,n) Trie::insert(readint());
printf("%d\n",Trie::dfs(1,1,bound));
return 0;
}
拓展
即第二题的做法。考虑 min ( a i ⊕ a j ) \min(a_i\oplus a_j) min(ai⊕aj) 的判定问题,和 max \max max 很像,唯一的区别是,其判断依据为 “子树内是否有至少两个数” 而非 “是否存在该子树” 了。
所以它甚至不需要预处理 2 k 2^k 2k,因为没有这样的选项。
BONUS:若 a ⩽ b ⩽ c a\leqslant b\leqslant c a⩽b⩽c 则 min ( a ⊕ b , b ⊕ c ) ⩽ a ⊕ c \min(a\oplus b,b\oplus c)\leqslant a\oplus c min(a⊕b,b⊕c)⩽a⊕c 。证明很简单,考虑 a , c a,c a,c 最高的不相同的 bit \text{bit} bit,由 a ⩽ b ⩽ c a\leqslant b\leqslant c a⩽b⩽c 可知 b b b 的前缀与二者都相同;这一位上 b b b 取 0 0 0 则 a ⊕ b a\oplus b a⊕b 这一位上是 0 0 0,取 1 1 1 则 b ⊕ c b\oplus c b⊕c 这一位上是 0 0 0,但 a ⊕ c a\oplus c a⊕c 这一位是 1 1 1,证毕。
于是可以排序后直接记 f ( v ) f(v) f(v) 表示以 v v v 结尾的子序列的选法。放在 t r i e \tt trie trie 上进行判定。复杂度没变;只是更好写。
核心代码
int dfs_forked(int x,int y,const llong &bound,int d){
if(!x || !y || d == -1) return 0;
if(bound>>d&1) // have to be 1
return (dfs_forked(ch[x][0],ch[y][1],bound,d-1)
+dfs_forked(ch[x][1],ch[y][0],bound,d-1))%MOD;
// if minimum is 1 on this bit
int res = int((llong(siz[ch[x][0]])*siz[ch[y][1]]
+llong(siz[ch[x][1]])*siz[ch[y][0]])%MOD);
// if minimum is 0 on this bit
modAddUp(res,dfs_forked(ch[x][0],ch[y][0],bound,d-1));
modAddUp(res,dfs_forked(ch[x][1],ch[y][1],bound,d-1));
return res; // can only choose 1 of each
}
int dfs(int x,const llong &bound,int d=LOGX-1){
if(!x || d == -1) return 0;
if(bound>>d&1) // have to be 1 (fork)
return dfs_forked(ch[x][0],ch[x][1],bound,d-1);
// if minimum is 1 on this bit
int res = int(llong(siz[ch[x][0]])*siz[ch[x][1]]%MOD);
// if minimum is 0 on this bit
const int lson = dfs(ch[x][0],bound,d-1);
const int rson = dfs(ch[x][1],bound,d-1);
// case 1: provided by <0,0> and <1,1>
res = int((res+llong(lson)*rson)%MOD);
// case 2: provided by <0,0>
res = int((res+llong(lson)*(siz[ch[x][1]]+1))%MOD);
// case 3: provided by <1,1>
res = int((res+llong(rson)*(siz[ch[x][0]]+1))%MOD);
return res;
}