内部通道(jzyz P6035),与原题唯一不同在于一个也不选也算一种方案。
首先挖掘性质。将\(a_i\)从小到大排序后,\(a_i\oplus a_j\)的最小值一定在某一对相邻\(a_i\),即\(a_i\oplus a_{i+1}\)处取到。
简易证明:排过序后,对于相邻的\(i,j,k\),从最高位去考虑,要么是\(0,0,1\),要么是\(0,1,1\),那么选\(i,j\)或\(j,k\)一定不会比选\(i,k\)大。由此,若任意相邻位置满足条件,则整个序列都满足条件。
那么设\(dp_i\)表示以\(a_i\)结尾的方案数。根据以上性质显然有:\(dp_i=1+\sum_{a_j<a_i}dp_j[a_j\oplus a_i\ge m]\)。
考虑在 Trie 上DP。这个我以前还真没怎么写过,所以记录一下代码实现。
首先,记录\(val_u\)表示 Trie 上\(u\)号点代表路径的\(dp\)值之和。即在 Trie 上插入\(a_i\)对应的\(dp_i\)时,令路径上每个节点\(u\)的\(val\)值加上\(dp_i\)。
为了求\(dp_i\)在 Trie 上查询时,考察\(m\)二进制下当前位:
若为\(1\),则这一位必须与\(a_i\)的对应位不同,直接移动指针经过与\(a_i\)状态不同的边。
若为\(0\),则这一位无限制,由于排了序,当前Trie上插入的都比\(a_i\)小,直接累加与\(a_i\)状态不同的边到的节点的\(val\),然后移动指针经过与\(a_i\)状态相同的边。
点击查看代码
int n;
ll m, a[N], dp[N], son[N * 60][2], val[N * 60];struct Trie{int tt = 1;void insert(ll x, ll k){int p = 1;for(int i = 59; i >= 0; i--){int y = ((x >> i) & 1);if(! son[p][y]) son[p][y] = ++tt;p = son[p][y];(val[p] += k) %= mod;}}ll ask(ll x, ll lim){int p = 1;ll rs = 0ll;for(int i = 59; i >= 0; i--){int y = ((x >> i) & 1), z = ((lim >> i) & 1);if(z) p = son[p][1 - y];else rs += val[son[p][1 - y]], p = son[p][y];rs %= mod;}(rs += val[p]) %= mod;return rs;}
}T;signed main(){read();n = read(), m = readll();for(int i = 1; i <= n; i++) a[i] = readll();sort(a + 1, a + n + 1);ll res = 0ll;for(int i = 1; i <= n; i++){dp[i] = T.ask(a[i], m) + 1ll;dp[i] %= mod;T.insert(a[i], dp[i]);res += dp[i];res %= mod;}cout << (res + 1) % mod;return 0;
}