• 【题解】异或粽子&加强版


    题目链接

    不愧是大常数选手,无论原题还是加强都要开 O2 才能过去 TAT

    Statement

    给定一个长度为 (n) 的序列,求前 (k) 大的区间异或和。 (1leq nleq 5e5,1leq kleq min(frac{n(n-1)}{2},2e5),0leq a_i<2^{32})

    Solution

    虽然是十二省联考,但是题不难,也不是很毒瘤。

    考虑区间异或和显然可以转化成两个前缀和的异或,那么现在的问题就变成了求前 (k) 大的异或对。由于这里是有序对,我们可以在开头把 (k imes 2) ,最后再让 (ans/=2) ,这样就可以直接统计无序点对了。

    题目中有 (a_i<2^{32}) 的限制,容易想到用 Trie 来处理二进制数位。

    首先,对于每个右端点,将使异或和取到最大值的左端点位置+异或和放进堆里。

    然后,每次显然是取堆顶的一对。考虑如何在取出之后放入该右端点的次大值。一个显然的想法是把之前的删除,但是这样时空和实现都很麻烦。

    考虑一个更简单的做法:其实 Trie 上不一定只能查询最值!我们也可以在 Trie 上用类似平衡树查询第 (p) 大的思想来找特定串的第 (k) 大异或和。

    具体做法:在向 Trie 中插入的时候顺便统计子树大小。

    对于一个原串在 Trie 上走到了 (g[pos][ch]) 的点,找最大值的时候是往 (g[pos][choplus 1]) 走,由于是从高位到低位,所以节点 (g[pos][choplus 1]) 子树中的任何串,和原串的异或和都比 (g[pos][ch]) 中的串要大。

    那么如果当前要求第 (p) 大的异或和,而 (siz[g[pos][choplus 1]]<p) ,显然答案应该在 (g[pos][ch]) 中,那么 (pos-=siz[g[pos][choplus 1]]) ,然后向 (g[pos][ch]) 走;

    否则加上当前位的贡献 (|=1<<i) ,向 (g[pos][choplus 1]) 走。代码如下:

     if ( siz[g[pos][ch^1]]<p ) p-=siz[g[pos][ch^1]],pos=g[pos][ch];
    else res|=1ll<<i,pos=g[pos][ch^1];
    

    这样就只需要记录所有右端点当前用到了第几大 (rk[i]) ,在弹出一个右端点的时候 (rk[i]++) ,并查询对应 (rk[i]) 的异或和,插回堆里面即可。

    Code

    //Author:RingweEH
    const int N=500010,M=N*35;
    struct Node
    {
        int pos; ll val;
        Node ( int _pos=0,ll _val=0 ) : pos(_pos),val(_val) {}
        bool operator < ( const Node &tmp ) const { return val<tmp.val; }
    };
    priority_queue<Node> q;
    int n,k,tot,rk[N],g[M][2],siz[M];
    ll ans,a[N],b[N];
     
    void Insert( ll x )
    {
        int pos=0;
        for ( int i=33; ~i; i-- )
        {
            int ch=(x>>i)&1;
            if ( !g[pos][ch] ) g[pos][ch]=++tot;
            pos=g[pos][ch]; siz[pos]++;
        }
    }
     
    ll Query( ll x,int p )
    {
        int pos=0; ll res=0;
        for ( int i=33; ~i; i-- )
        {
            int ch=(x>>i)&1;
            if ( siz[g[pos][ch^1]]<p ) p-=siz[g[pos][ch^1]],pos=g[pos][ch];
            else res|=1ll<<i,pos=g[pos][ch^1];
        }
        return res;
    }
     
    int main()
    {
        n=read(); k=read(); k<<=1;
        Insert( 0 );
        b[0]=0;
        for ( int i=1; i<=n; i++ )
        {
            a[i]=read(); b[i]=b[i-1]^a[i];
            Insert( b[i] );
        }
    
        for ( int i=0; i<=n; i++ )
        {
            ll tmp=Query(b[i],rk[i]=1);
            q.push( Node(i,tmp) );
        }
        ll ans=0;
        while ( k-- )
        {
            ans+=q.top().val;  int p=q.top().pos; q.pop();
            ll tmp=Query(b[p],++rk[p]);
            q.push( Node(p,tmp) );
        }
     
        printf( "%lld
    ",ans>>1 );
     
        return 0;
    }
    

    加强版

    Orz Apocrypha 为什么它也出现在了我们的模拟赛里

    范围:(nleq 7.5*10^5,kleq frac{n*(n+1)}{2},a_i<2^{32}) . 于是需要一个 (Omicron(nlog n)) 的优秀做法。

    Solution

    同样做一遍异或前缀和,记为 (b) ,将题目转化为点对异或。有序对和无序对的处理同上。

    考虑到 (K) 很大,显然不能枚举,那么可以枚举每一位计算贡献。大致思路就是,找出异或值第 (K) 大的数,然后求所有大于这个数的异或值之和。在 Trie 上找第 (K) 大的思路同上,但是中间要多统计一些东西:

    同原题一样,首先我们要记录一个 (siz) 表示 Trie 树上节点的子树大小(注意是有效的结束节点个数),统计方法同上。

    设当前节点为 (pos) ,当前这一位的值是 (ch) ,那么只有在 (g[pos][choplus 1]) 这个节点的子树中的 (b) 才能在这一位上产生贡献。

    如果出现 (siz[g[pos][choplus 1]]<K) ,即上文需要减去 (siz) 的情况,再在 (g[pos][choplus 1]) 处打一个 (b[i]) 的标记,表示子树中所有的 (b[j]oplus b[i]) 都会对答案产生贡献,记录 (ans[bit]+=siz[g[pos][choplus 1]]) ,表示二进制下这一位上 (1) 的个数。

    否则,(ans[bit]+=K)

    这样做完之后,再 DFS 一遍,处理每个标记,然后加入答案的贡献即可,或者直接顺带统计也行。但是在统计答案的时候,处理标记仍然需要拆位,复杂度 (Omicron(nlog ^2n)) ,还需要优化。

    考虑打标记是什么形式。能够在某个点上打上标记的数的集合一定都在某一棵子树里面,那么标记就转化成了两棵子树中所有 (b[i]) 的两两异或和。仍然拆位维护,统计子树中某一位上 (1) 的个数,在插入之前将 (b) 排序,这样每个子树就对应一段区间,比较好处理。

    复杂度之所以是 (nlog) 的,是因为标记个数不超过 (Omicron(n)) 个。标记只会在三度点上出现。因此,一个节点 (x) 至多只会和另外一个点打标记。那么就不需要去重。

    Code

    小范围(但是保证时间复杂度与 (K) 无关)的提交地址:CF241B

    这里是正经大范围的代码:数据自己造XD

    我要杀了出题人!不知道为什么我的程序在所有数相同的时候会 WA ,然后特判了还是过不去,结果发现就算是这样的部分分也要开 Int128 !骗个分容易吗。

    //Author:RingweEH
    #define Int128 __int128
    const int N=750010,M=32;
    int n,tot=1,g[N*M][2],siz[N*M],bit[N][M],nw[N];
    ll lim,a[N],m,cnt;
    Int128 sum;
     
    void Insert( int x )
    {
        int pos=1;
        for ( int i=31; ~i; i-- )
        {
            int ch=x>>i&1;
            if ( !g[pos][ch] ) g[pos][ch]=++tot;
            pos=g[pos][ch]; siz[pos]++;
        }
    }
     
    void GetLim()
    {
        ll las=m;
        for ( int i=1; i<=n; i++ )
            nw[i]=1;
        for ( int i=31; ~i; i-- )
        {
            ll tmp=0;
            for ( int j=1; j<=n; j++ )
            {
                int ch=a[j]>>i&1;
                tmp+=siz[g[nw[j]][ch^1]];
            }
            if ( tmp>=las ) lim|=(1ll<<i);
            else las-=tmp;
            for ( int j=1; j<=n; j++ )
            {
                int ch=(a[j]^lim)>>i&1;
                nw[j]=g[nw[j]][ch];
            }
        }
    }
     
    void GetSum()
    {
        for ( int i=31; ~i; i-- )
        {
            if ( lim>>i&1 ) continue;
            ll now=((lim>>i)<<i)|(1ll<<i);
            for ( int j=1; j<=n; j++ )
            {
                ll l=((a[j]^now)>>i)<<i,r=l|((1ll<<i)-1);
                l=lower_bound( a+1,a+1+n,l )-a;
                r=upper_bound( a+1,a+1+n,r )-a;
                sum+=now*(r-l);
                cnt+=r-l;
                if ( l<=j && j<r ) cnt--;
                for ( int k=i-1; ~k; k-- )
                {
                    int tmp=bit[l][k]-bit[r][k];
                    if ( (a[j]>>k)&1 ) tmp=r-l-tmp;
                    sum+=(1ll<<k)*tmp;
                }
            }
        }
    }
     
     
    void print( Int128 x)
    {
        if ( !x ) return;
        print(x/10);
        putchar( (char)(x%10+'0') );
    }
     
    void Same()
    {
        int cnt=0; ll num=0;
        for ( int i=1; i<=n; i++ )
            if ( a[i]==0 ) num+=(i-1)-cnt,cnt++;
            else num+=cnt;
        m>>=1;
        Int128 res=a[2],mul=min( m,num );
        print( res*mul );
    }
     
    int main()
    {
        n=read()+1; m=read()<<1;  a[1]=0;
        bool fl=1; ll a1;
        for ( int i=2; i<=n; i++ )
        {
            ll x=read(); a[i]=x^a[i-1];
            if ( i==2 ) a1=x;
            else fl&=(a1==x);
        }
     
        if ( fl ) { Same(); return 0; }
     
        sort( a+1,a+1+n );
        for ( int i=n; i; i-- )
        {
            Insert( a[i] );
            for ( int j=0; j<32; j++ )
                bit[i][j]=bit[i+1][j]+((a[i]>>j)&1);
        }
     
        GetLim(); GetSum();
        sum>>=1; cnt>>=1; m>>=1;
        Int128 ans=(sum+(m-cnt)*lim);
     
        print( ans );
     
        return 0;
    }
    
  • 相关阅读:
    一步一步实现一个简单的OS(简单的让boot载入setup)
    hdu 1203 I NEED A OFFER!(01背包)
    面向服务与微服务架构
    ThreadLocal,LinkedBlockingQueue,线程池 获取数据库连接2改进
    deeplearning.net 0.1 document
    怎样有效的降低反复的代码
    redmine 自己定义字段mysql表结构
    c语言中使用宏,需要注意的的几点
    gcc自有的define语法,解决变量多次自加的问题
    ubuntu16.04 安装docker
  • 原文地址:https://www.cnblogs.com/UntitledCpp/p/YiHuoZongZi.html
Copyright © 2020-2023  润新知