【JZOJ4021】【CF251D】Two Sets(异或方程组+高斯消元)

Problem

  给出n(≤100000)个数字,要求将其分成两个集合,集合1的异或和为x1,集合2的异或和为x2,求出一种划分方案,使得在x1+x2最大的前提下x1尽量小。

Solution

  这道题我刚看感觉很不可做,于是打了个暴力。
  正解竟然是神奇的高斯消元。
  首先,我们瞄到“异或”这个词,马上联想到位运算,继而联想到从高到低逐位来搞。当然我在比赛时也想到了这一点。(虽然也就想到了这一点
  于是,我们可以构建64个方程,方程fj形如:a1x1^a2x2^…^anxn=1/0。其中,xi表示第i个数在二进制下的第j位,ai表示是否要将第i个数分给集合1;而若在所有数中,有奇数个的第j位为1,则方程的右边为0,反之则反。因为若有奇数个数的第j位为1,则划分为两个集合后,肯定是一个集合为奇数,另一个集合为偶数,而又要使x1尽量小,所以将0分配给集合1;若有偶数个,则划分为两个集合后,可能两个都是奇数,也可能都是偶数,那我们要让x1+x2最大,所以最好都分配为奇数,于是异或和为1。
   然后就开始上高斯消元。注意,我们必须优先解答案为1的方程,原因下面再说。由于要满足黑体的那句话,我们要以一种奇特的方式解方程。
   我们先枚举j从0到1,表示那个位置有偶数/奇数个1;然后将答案为!j的方程(从高到低逐位放)塞进方程组,设其为f[tot]。然后,我们用上面的方程消当前方程,也即f[tot]^=f[k]。若消完后,f[tot]的左边的所有系数均变成0了,则说明这个方程费了——要么与上面的方程本质相同,要么就不能成立。这样就删掉这个方程。若还有非0的系数,则用这个方程“反哺”上面的方程。
   这样的话,由于我们是先搞偶数方程,所以会先满足偶数方程。因为如果某个奇数方程被之前的偶数方程消完了,但是它的答案又为1,则说明它不可能成立,那我们就要无视掉这个方程,去满足那些偶数方程——因为满足一个偶数方程会产生两个1的收益,而我们必须保证x1+x2最大;而若某个方程被同类方程消完了,但答案亦为1,则说明它又不能成立,我们要优先使前面的方程成立,因为我们是从高到低逐位做的。
   最后,所有方程的非0系数均会被消成≤1个,则那个位置的未知数就直接为答案;否则为自由元。由于本题方程数只有64,而未知数有n个,所以至少会有n-64个之多的自由元。对于它们,随便取即可。
   当然,由于它是异或方程组,所以我们可以用c++的bitset优化,这样可以去掉一个log的复杂度。
   时间复杂度: O ( n l o g 2 10 18 )

Code

#include <cstdio>
#include <bitset>
using namespace std;
#define N 100010
#define M 64
#define ll long long
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fd(i,a,b) for(i=a;i>=b;i--)
int i,j,k,n,tot,fir[M],ans[N];
ll num,xs,x;
bitset<N>map[M+1],f[M+1];
int main()
{
    scanf("%d",&n);
    fo(i,1,n)
    {
        scanf("%lld",&num);
        xs^=num;
        x=1;
        fo(j,0,M)
        {
            map[j][i]=x&num;
            x<<=1;
        }
    }
    fo(j,0,1)
        fd(i,M,0)
            if(((xs>>i)&1)==j)
            {
                f[++tot]=map[i];fir[tot]=n+2;f[tot][n+1]=!j;
                fo(k,1,tot-1)
                    if(f[tot][fir[k]])
                        f[tot]^=f[k];
                fo(k,1,n)
                    if(f[tot][k])
                    {
                        fir[tot]=k;
                        break;
                    }
                if(fir[tot]>n)
                {
                    tot--;
                    continue;
                }
                fo(k,1,tot-1)
                    if(f[k][fir[tot]])
                        f[k]^=f[tot];
            }
    fo(i,1,tot)ans[fir[i]]=f[i][n+1];
    fo(i,1,n)printf("%d ",2-ans[i]);
}

猜你喜欢

转载自blog.csdn.net/qq_36551189/article/details/79679920