HDU 5270 归并排序

问题描述
ZYB喜欢研究Xor,现在他得到了两个长度为
  
  
   
   n
  
  的数组A,B。于是他想知道:对于所有数对
  
  
   
   (i,j)(i[1,n],j[1,n])
  
  
  
  
   
   (Ai+Bj)
  
  的xor之和为多少
定义多个数
  
  
   
   A1
  
  ,
  
  
   
   A2
  
  ...
  
  
   
   Ak
  
  
  
  
   
   xor
  
  之和为
  
  
   
   A1xorA2xorA3xor...xorAk
  
  
输入描述
一共
  
  
   
   T
  
  (
  
  
   
   T10
  
  )组数据,对于每组数据:
第一行一个正整数
  
  
   
   n
  
  ,表示数组长度
第二行
  
  
   
   n
  
  个非负整数,第
  
  
   
   i
  
  个整数为
  
  
   
   Ai
  
  
第三行
  
  
   
   n
  
  个非负整数,第
  
  
   
   i
  
  个整数为
  
  
   
   Bi
  
  

  
  
   
   n[1,105]
  
  
  
  
   
   Ai,Bi[0,260]
  
  
保证所有
  
  
   
   n
  
  的和小于等于
  
  
   
   2105
  
  
输出描述
每组数据输出一行Case #x: ans。x表示组数编号,从1开始。ans为所求值。
输入样例
1
5
8 11 30 28 0
28 27 22 0 0
输出样例
Case #1: 34
1003 ZYB loves Xor II
我们考虑两个数
   
   
    
    A
   
   
   
   
    
    B
   
   。
为了描述方便,我们设[P]的值为:当表达式P的值为真时,[P]=1,否则[P]=0
我们现在考虑计算
   
   
    
    [(A+B)and(2i)>0]
   
   
首先我们将A,B都对
   
   
    
    2i+1
   
   取模,显然这样是不会影响答案的
则有一个十分显然的等式:

   
   
    
    [(A+B)and(2i)>0]=[(A+B)(2i)][(A+B)(2i+1)]+[(A+B)(32i)]
   
   
这个式子相当容易理解,这里不多述了
考虑每一位对答案的贡献是独立的,我们每一位分开做
于是现在问题变成了:给定数组
   
   
    
    A,B
   
   ,求满足
   
   
    
    Ai+Bjlimit
   
   的数对个数
我们可以将
   
   
    
    A,B
   
   排序后,直接
   
   
    
    O(n)
   
   计算即可
然而排序是
   
   
    
    O(nlogn)
   
   的,这样总复杂度就是
   
   
    
    O(nlognlogA)
   
   了,无法通过此题
于是这里有个小技巧
我们从高位往低位做,现在我们要实现的是:将
   
   
    
    A
   
   中每个数对
   
   
    
    P
   
   取模后将
   
   
    
    A
   
   排序
我们发现
   
   
    
    A
   
   会被分成两段,一段小于
   
   
    
    P
   
   ,一段大于等于
   
   
    
    P
   
   ,只有后面一段要取模,我们可以取模后直接将这两段归并,复杂度是
   
   
    
    O(n)
   
   的
时间复杂度:
   
   
    
    O(nlogA+nlogn)
   
   

此题注意位运算的大量使用,很省时间,而且容斥的想法和转化很巧妙。

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string.h>
#include<vector>
#include<algorithm>
#define ll __int64
#define maxn 110000
using namespace std;

vector<ll>q[2];
ll x[maxn],y[maxn];
ll bit[100];
int n;

ll solve()//我擦。。这个归并太吊了
{
    ll ans=0;
    for(ll i=0;i<62;i++)
    {
        ll tot=0;
        q[0].clear();q[1].clear();
        for(ll j=1;j<=n;j++)
            q[(x[j]>>i)&1].push_back(x[j]);
        for(ll j=0;j<q[0].size();j++)
            x[++tot]=q[0][j];
        for(ll j=0;j<q[1].size();j++)
            x[++tot]=q[1][j];
        tot=0;
        q[0].clear();q[1].clear();
        for(ll j=1;j<=n;j++)
            q[(y[j]>>i)&1].push_back(y[j]);
        for(ll j=0;j<q[0].size();j++)
            y[++tot]=q[0][j];
        for(ll j=0;j<q[1].size();j++)
            y[++tot]=q[1][j];
        ll len1=1,len2=1,len3=1;
        ll limit1=1ll<<i;
        ll limit2=1ll<<(i+1);
        ll all=(1ll<<(i+1))-1;///这里加1LL
//        cout<<all<<" all"<<endl;
        ll sum=0;
//        for(int i=1;i<=tot;i++) cout<<x[i]<<" ";
//        cout<<endl;
//        for(int i=1;i<=tot;i++) cout<<(x[i]&all)<<" ";
//        cout<<endl;
//        for(int i=1;i<=tot;i++) cout<<y[i]<<" ";
//        cout<<endl;
        for(ll i=n;i>=1;i--)
        {
            while((((x[i]&all)+(y[len1]&all))<limit1) && (len1<=n)) len1++;//这里多加几个()
            while((((x[i]&all)+(y[len2]&all))<limit2) && (len2<=n)) len2++;
            while(((x[i]&all)+(y[len3]&all)<limit1+limit2) && (len3<=n)) len3++;//cout<<(x[i]&all)<<" "<<(y[len3]&all)<<" "<<len3<<" "<<y[len3]<<" "<<(limit1+limit2)<<endl;
            sum+=n-(len1-1-len2+1+len3-1);
//            cout<<len1<<" "<<len2<<" "<<len3<<endl;
//            cout<<n-(len1-1-len2+1+len3-1)<<endl;
        }
//        cout<<sum<<" sum   "<<i<<endl;
        if(sum&1) ans+=bit[i];
    }
    return ans;
}

int main()
{
//    freopen("1003.txt","r",stdin);
    int cas;
    scanf("%I64d",&cas);
    bit[0]=1;
    for(ll i=1;i<=61;i++)
    bit[i]=bit[i-1]*2;
    for(ll ca=1;ca<=cas;ca++)
    {
        scanf("%I64d",&n);
        for(ll i=1;i<=n;i++)
            scanf("%I64d",&x[i]);
        for(ll i=1;i<=n;i++)
            scanf("%I64d",&y[i]);
//        cout<<solve()<<endl;
       printf("Case #%I64d: %I64d\n",ca,solve()) ;
    }
}



                
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值