【问题描述】
给出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
的第位:
如果第
i
i
位为1,那么这一位肯定有一个1,对于
x1+x2
x
1
+
x
2
贡献固定
如果第
i
i
位为0,有两种情况:或
x1=x2=0
x
1
=
x
2
=
0
为了解决以上问题,我们需要求出线性基
为什么?
因为线性基可以有效的缩小数集,保证异或和相等且一定是通过原数集中的数组合得到的
首先我们要最大化
x1+x2
x
1
+
x
2
,显然要从高位到低位枚举
对于一个数
a
a
,在加入线性基的时候
- 我们优先考虑X的零位,我们当然希望这一位上
如果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; }
- 如果遍历过所有X的零位后,都没有给a找到一个插入位置