5794: 划分
时间限制: 1 Sec 内存限制: 128 MB提交: 18 解决: 6
[ 提交][ 状态][ 讨论版]
题目描述
给出n个不超过m的非负整数,将数划分成两个集合,记为1号集合和2号集合。x1为1号集合中所有数的异或和,x2为2号集合中所有数的异或和。在最大化x1+x2的前提下,最小化x1。
输入
第一行n
第二行n个非负整数
第二行n个非负整数
输出
一行两个数,第一个数是x1,第二个数是x2
样例输入
7
1 1 2 2 2 3 3
样例输出
1 3
提示
对于 30%的数据,n<=10
对于 60%的数据,n<=1000
对于 100%的数据,n≤105,m≤1018
来源
赛后补了线性基的相关知识,发现自己还是跟SB一样不太懂线性基,但是大概懂了线性基的一个用法。
推荐线性基博客:http://www.cnblogs.com/ljh2000-jump/p/5869991.html
题解:
先求出所有数的异或和。从高位向低位枚举,若这一位是0,则考虑在划分集合的时候能否使每个集合中这两个数都是1。再从高位向低位枚举,若这一位是1,则考虑能否让x1的这一位是0。
划分为两个集合,相当于选出x1。枚举到一位时,要将所有这一位是1的数都异或一个出现过的这一位是1的数。这样保证剩下的数都小于当前枚举到的那一位,便于求解。(线性基)
我感觉是求出来一个线性基,尽量让那些大的都给x2,然后在给x1,就像上边说的那样如果0对应2个1这样的是肯定跑不了的。
long long a[100005];
long long b[100];
long long ans;
long long x1,x2;
int n;
void solve()
{
for(int i=1;i<=n;i++)
{
int flag=1;
long long t=a[i];
for(int j=63;j>=0;j--)
{
if(((t>>j)&1) && (!((ans>>j)&1)))//当前这个数的j位是1 但是 异或和是0 表示 是1 1 一定有贡献
{
if(b[j])
t=t^b[j];
else
{
b[j]=t;
flag=0;
break;
}
}
}
if(!flag)
continue;
//如果没找到上边的那种情况
for(int j=63;j>=0;j--)//去寻找那种 ans是1的而且当前也是1的 存一下
{
if(((t>>j)&1) && ((ans>>j)&1))
{
if(b[j])
t=t^b[j];
else
{
b[j]=t;
break;
}
}
}
}
x1=0;
x2=0;
for(int j=63;j>=0;j--)//先去寻找那些 ans==0 而且x2当前第J位是0的 然后看下当前b[j]是否有数 有的话就加上
{
if((!((ans>>j)&1) && (!((x2>>j)&1))))
{
if(b[j])
{
x2=x2^b[j];
}
}
}
for(int i=63;i>=0;i--)
{
if(((ans>>i)&1)&&(!((x2>>i)&1)))//去找ans是1的 而且x2的j位还没有的加上 跟上边不一样 上边是0分解11的 这个是1分10的
{
if(b[i])
x2=x2^b[i];
}
}
x1=ans^x2;
}
int main()
{
ans=0;
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%lld",&a[i]);
ans=ans^a[i];
}
solve();
printf("%lld %lld\n",x1,x2);
return 0;
}