ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

CodeForces 1616H Keep XOR Low {a^b≤x} / CodeForces gym102331 Bitwise Xor {a^b≥x}(trie树 + 计数)

2022-01-14 09:04:27  阅读:233  来源: 互联网

标签:XOR trie rson CodeForces dfs int lson now


文章目录

CodeForces 1616H Keep XOR Low

problem

洛谷链接

solution

虽然选的是一个子集,但本质还是二元限制。

这非常类似以前做过的题目,已知 a i , k a_i,k ai​,k,求满足 a i ⊕ a j ≤ k a_i\oplus a_j\le k ai​⊕aj​≤k 的 a j a_j aj​ 个数。

这是一元限制,常见解法是在 trie \text{trie} trie 树上统计。 紧贴 的思想。

具体而言,就像数位 d p dp dp 一样,贴着 x x x 二进制位的要求走,如果 d d d 位为 1 1 1,那么直接统计 trie \text{trie} trie 树该位 0 0 0 子树的个数,然后继续往 1 1 1 那边走。

我们考虑采用同样的方法来解决本题。只不过此时的 a i a_i ai​ 同样未知。

直接设计 d f s ( x , y , d ) : dfs(x,y,d): dfs(x,y,d): 目前在 d d d 二进制位,从 x , y x,y x,y 子树中选取子集,仅考虑 x − y x-y x−y 间限制的方案数,且前 d d d 位都是紧贴的。

首先得从高到低考虑二进制位。初始调用函数 d f s ( 1 , 1 , 30 ) dfs(1,1,30) dfs(1,1,30)。(插入以及统计子树内个数就略过了)

  • x = y x=y x=y。

    • k k k 的 d d d 位为 1 1 1。

      直接返回 d f s ( l s o n [ x ] , r s o n [ x ] , d − 1 ) dfs(lson[x],rson[x],d-1) dfs(lson[x],rson[x],d−1)。

    • k k k 的 d d d 位为 0 0 0。

      返回 d f s ( l s o n [ x ] , l s o n [ x ] , d − 1 ) + d f s ( r s o n [ x ] , r s o n [ y ] , d − 1 ) dfs(lson[x],lson[x],d-1)+dfs(rson[x],rson[y],d-1) dfs(lson[x],lson[x],d−1)+dfs(rson[x],rson[y],d−1)。

      这里不能乘法乱来,否则就会包含 x x x 左右儿子组合等等,这些异或在该位可是 1 1 1,超过了 k k k 限制。

  • x ≠ y x\ne y x​=y。

    • k k k 的 d d d 位为 1 1 1。

      返回 d f s ( l s o n [ x ] , r s o n [ y ] , d − 1 ) ∗ d f s ( r s o n [ x ] , l s o n [ y ] , d − 1 ) dfs(lson[x],rson[y],d-1)*dfs(rson[x],lson[y],d-1) dfs(lson[x],rson[y],d−1)∗dfs(rson[x],lson[y],d−1)。

      实际上应该是两边都要 + 1 +1 +1 再乘,表示仅从 x , y x,y x,y 的各一个儿子中选取的方案数,直接乘就是要求 x , y x,y x,y 左右儿子中都要选择了。

      最后还要 − 1 -1 −1,减去两边没一个选的空集。

      这个乘法就包含了 x x x 左右儿子组合, y y y 左右儿子组合, x , y x,y x,y 左/右儿子组合等等的可能。

      这些组合在这一位的异或都是 0 0 0,都是松弛在限制下面的。

    • k k k 的 d d d 位为 0 0 0。

      返回 d f s ( l s o n [ x ] , l s o n [ y ] , d − 1 ) + d f s ( r s o n [ x ] , r s o n [ y ] , d − 1 ) dfs(lson[x],lson[y],d-1)+dfs(rson[x],rson[y],d-1) dfs(lson[x],lson[y],d−1)+dfs(rson[x],rson[y],d−1)。

      但这里没有考虑到只选 x x x 内部的左右子树和只选 y y y 内部的左右子树的情况。

      直接加上 ( 2 s i z [ l s o n [ x ] ] − 1 ) ( 2 s i z [ r s o n [ x ] ] − 1 ) + ( 2 s i z [ l s o n [ y ] ] − 1 ) ( 2 s i z [ r s o n [ y ] ] − 1 ) (2^{siz[lson[x]]}-1)(2^{siz[rson[x]]}-1)+(2^{siz[lson[y]]}-1)(2^{siz[rson[y]]}-1) (2siz[lson[x]]−1)(2siz[rson[x]]−1)+(2siz[lson[y]]−1)(2siz[rson[y]]−1)。

      为什么这里可以直接乘了,而不是继续 d f s dfs dfs 下去呢?

      注意到如果 x ≠ y x\neq y x​=y,那么之前必定是有一个更高位使得他们分叉。

      而分叉的条件都是那个更高位上的 k k k 是 1 1 1。

      所以这里可以直接 x / y x/y x/y 子树内部各自乱选,反正异或起来至少在那个较高位都是 0 0 0。

      一定是被限制松弛的方案数。

code

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define mod 998244353
#define maxn 150005
int n, m, root, cnt;
int a[maxn], mi[maxn], l[maxn * 30], r[maxn * 30], siz[maxn * 30];

void insert( int &now, int d, int x ) {
    if( ! now ) now = ++ cnt;
    siz[now] ++;
    if( ! ~ d ) return;
    if( x >> d & 1 ) insert( r[now], d - 1, x );
    else insert( l[now], d - 1, x );
}

int dfs( int x, int y, int d ) {
    //-1是减去空集的情况数
    if( ! x ) return mi[siz[y]] - 1;
    if( ! y ) return mi[siz[x]] - 1;
    if( x == y ) {
        if( ! ~ d ) return mi[siz[x]] - 1;
        else if( m >> d & 1 ) 
            return dfs( l[x], r[x], d - 1 );
        else 
            return ( dfs( l[x], l[x], d - 1 ) + dfs( r[x], r[x], d - 1 ) ) % mod;
    }
    else {
        if( ! ~ d ) return mi[siz[x]] * mi[siz[y]] % mod - 1;
        //+1乘是有可能不选这个子树中的子集
        else if( m >> d & 1 ) 
            return ( dfs( l[x], r[y], d - 1 ) + 1 ) * ( dfs( r[x], l[y], d - 1 ) + 1 ) % mod - 1;
        else {
            int ans = ( dfs( l[x], l[y], d - 1 ) + dfs( r[x], r[y], d - 1 ) ) % mod;
            ( ans += ( mi[siz[l[x]]] - 1 ) * ( mi[siz[r[x]]] - 1 ) % mod ) %= mod;
            ( ans += ( mi[siz[l[y]]] - 1 ) * ( mi[siz[r[y]]] - 1 ) % mod ) %= mod;
            //这种情况计数是左右子树一定都选了的
            return ans;
        }
    }
}

signed main() {
    scanf( "%lld %lld", &n, &m ); mi[0] = 1;
    for( int i = 1, x;i <= n;i ++ ) {
        scanf( "%lld", &x ), insert( root, 30, x );
        mi[i] = ( mi[i - 1] << 1 ) % mod;
    }
    printf( "%lld\n", ( dfs( root, root, 30 ) + mod ) % mod );
    return 0;
}

还有一道类似的题目,只不过限制变为了 a i ⊕ a j ≥ k a_i\oplus a_j\ge k ai​⊕aj​≥k。但做法就完全不一样了。

CodeForces gym102331 Bitwise Xor

problem

CF链接

solution

一堆数按从高到低二进制考虑,按第 w w w 位分为 0 / 1 0/1 0/1 两个集合,第 w w w 位产生贡献肯定是两个数隶属不同的集合。

如此递归分层下去,我们发现:从小到大排序后只用考虑相邻两个数是否满足限制。

上述结论有一个数学化的表达: a < b < c ⇒ min ⁡ ( a ⊕ b , b ⊕ c ) ≤ a ⊕ c a<b<c\Rightarrow \min(a\oplus b,b\oplus c)\le a\oplus c a<b<c⇒min(a⊕b,b⊕c)≤a⊕c。

可以大概理解为数相差越大,二进制位的高位越有可能异或为 1 1 1,异或结果越有可能更大。

因此可以将 a a a 从小到大排序,设 f i : a i f_i:a_i fi​:ai​ 结尾的序列的方案数。

转移利用 trie \text{trie} trie 树的紧贴思想去求与 a i a_i ai​ 异或 ≥ x \ge x ≥x 的 a j a_j aj​ 对应的方案数。

code

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define mod 998244353
#define maxn 300005
int n, m;
int a[maxn], cnt;
int trie[maxn * 65][2], sum[maxn * 65];

void insert( int x, int val ) {
    for( int i = 60, now = 0;~ i;i -- ) {
        int k = x >> i & 1;
        if( ! trie[now][k] ) trie[now][k] = ++ cnt;
        now = trie[now][k];
        ( sum[now] += val ) %= mod;
    }
}

int query( int x ) {
    int ans = 0, now = 0;
    for( int i = 60;~ i;i -- ) {
        int k = x >> i & 1;
        if( m >> i & 1 ) now = trie[now][k ^ 1];
        else ( ans += sum[trie[now][k ^ 1]] ) %= mod, now = trie[now][k];
        if( ! now ) break; //很有可能这个数比之大的不存在 那么now就会回到0 如果继续走下去就岔了
    }
    return ( ans + sum[now] ) % mod;
}

signed main() {
    scanf( "%lld %lld", &n, &m );
    for( int i = 1;i <= n;i ++ ) scanf( "%lld", &a[i] );
    sort( a + 1, a + n + 1 );
    int ans = 0; //f[i]=1+\sum_{j,a_i^a_j>=m}f[j]
    for( int i = 1;i <= n;i ++ ) {
        int val = query( a[i] ) + 1; //+1是自己单独一个的方案
        ans = ( ans + val ) % mod;
        insert( a[i], val );
    }
    printf( "%lld\n", ans );
    return 0;
}

标签:XOR,trie,rson,CodeForces,dfs,int,lson,now
来源: https://blog.csdn.net/Emm_Titan/article/details/122486780

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有