正月十一hu测 T1.divide(线性基)

61 篇文章 0 订阅
8 篇文章 0 订阅

【问题描述】
给出n个不超过m的非负整数,将数划分成两个集合,记为1号集合和2号集合。x1为1号集合中所有数的异或和,x2为2号集合中所有数的异或和。
在最大化x1+x2的前提下,最小化x1。

【输入格式】
第一行n
第二行n个非负整数

【输出格式】
一行两个数,第一个数是x1,第二个数是x2

【样例输入】
7
1 1 2 2 2 3 3

【样例输出】
1 3

【数据规模和约定】
对于 30%的数据,n<=10
对于 60%的数据,n<=1000
对于 100%的数据,n≤10^5,m≤10^18

题外话: 一轮集训D1T1。。。orz
感觉自己就是zz,还有什么好不服气的
真的是弱爆了,以前的题都没有学好就开始学一些没有的东西,是个人都能超过我好吗
还是好好学吧,有时间生气,还是填填坑吧,不晚

分析:
求出 n n 个数的异或和X
对于 X X 的第i位:
如果第 i i 位为1,那么x1,x2这一位肯定有一个1,对于 x1+x2 x 1 + x 2 贡献固定
如果第 i i 位为0,有两种情况:x1=x2=1 x1=x2=0 x 1 = x 2 = 0

为了解决以上问题,我们需要求出线性基
为什么?
因为线性基可以有效的缩小数集,保证异或和相等且一定是通过原数集中的数组合得到的

首先我们要最大化 x1+x2 x 1 + x 2 ,显然要从高位到低位枚举
对于一个数 a a ,在加入线性基的时候

  • 我们优先考虑X的零位,我们当然希望这一位上x1=1,x2=1
    如果a[i]这一位上是1,我们就贪心得把ta塞入这个位置

    • 如果遍历过所有X的零位后,都没有给a找到一个插入位置
      我们就可以考虑X的非零位
    • 如此构造出来的线性基,通过合法的分配,一定能得到 x1+x2 x 1 + x 2 的最大值

      下面我们就要最小化 x1 x 1
      最大化 x2 x 2 等价于最小化 x1 x 1

      从高位到低位枚举
      接下来我们只考虑当前得到的 x2 x 2 的零位
      (如果我们在非零位上操作,异或一下这一位就变成了0,显然不会变优)
      对于当前 x2 x 2 的每一个零位
      (注意一定是当前 x2 x 2 ,因为在维护的时候 x2 x 2 是变化的)

      • 优先考虑X同样是零的位置,最大化这一位上一定是1: x=2b[i] x 2 = b [ i ]
      • 之后考虑X的非零位后,最大化这一位上一定是1: x=2b[i] x 2 = b [ i ]
      #include<cstdio>
      #include<cstring>
      #include<iostream>
      #define ll long long
      
      using namespace std;
      
      const int N=100005;
      int n;
      ll a[N],b[100],x1,x2,o,ans;
      
      void cal() {  //线性基 
          for (int i=1;i<=n;i++) {
              bool flag=1;
              ll x=a[i];
              for (int j=63;j>=0;j--) 
                  if ( ((x>>j)&1) && (! ((o>>j)&1) ) ) {    //优先选择o是0的位置 
                      if (b[j]) x^=b[j];
                      else {
                          b[j]=x;
                          flag=0; break;
                      }
                  }
              if (!flag) continue;         //已经在线性基中插入了 
              for (int j=63;j>=0;j--)
                  if ( ((x>>j)&1) && ((o>>j)&1) ) {
                      if (b[j]) x^=b[j]; 
                      else {
                          b[j]=x;
                          break;
                      }
                  } 
          }
      }
      
      void solve() {
          cal();       
          x1=0,x2=0;
          for (int i=63;i>=0;i--)  
              if ( (! ((o>>i)&1) ) && (! ((x2>>i)&1) ) )
                  if (b[i]) x2^=b[i];
          for (int i=63;i>=0;i--) 
              if ( ((o>>i)&1) && (! ((x2>>i)&1) ) )
                  if (b[i]) x2^=b[i]; 
          x1=o^x2; 
      }
      
      int main()
      {
          //freopen("divide.in","r",stdin);
          //freopen("divide.out","w",stdout);
          scanf("%d",&n); o=0;
          for (int i=1;i<=n;i++) 
              scanf("%lld",&a[i]),o^=a[i];
          solve();
          printf("%lld %lld",x1,x2);
          return 0;
      }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值