XOR1248 - Editorial

PROBLEM LINK:

Practice
Contest: Division 1
Contest: Division 2
Contest: Division 3
Contest: Division 4

Author: rainb0ysimp
Tester: yash_daga
Editorialist: iceknight1093

DIFFICULTY:

TBD

PREREQUISITES:

Familiarity with bitwise XOR

PROBLEM:

You’re given an array A.
The following move can be performed at most once:

  • Pick an index 1 \leq i \leq N, and write A_i = 2^{k_1} + 2^{k_2} + 2^{k_3} + \ldots + 2^{k_m} for some m \gt 0 (the powers needn’t be distinct).
    Then, delete A_i from the array and append all the 2^{k_j} to it.

Find the maximum possible bitwise XOR of the resulting array.

EXPLANATION:

Let’s fix i, the index we’re going to operate on.

Let X_i denote the bitwise XOR of the other N-1 elements.
X_i is easy to compute: it’s just (A_1\oplus A_2\oplus\ldots A_N)\oplus A_i, with the first term being a constant that only needs to be computed once.

Now that X_i is fixed, our task is to break A_i into powers of 2 such that the overall bitwise XOR is maximized.
As in most problems involving maximization (or minimization) of the result of bitwise operations, this can be done greedily from the highest bit to the lowest — this is because 2^0 + 2^1 + 2^2 + \ldots + 2^k \lt 2^{k+1}, so it’s always better to set a higher bit even at the cost of all lower bits.

So, we iterate over bits b from 60 down to 0; each time trying to decide whether it can be set in the resulting XOR or not.
That decision can be made as follows:

  • If X_i has bit b set, we need to ensure that there are an even number of 2^b in the split; so that they all cancel out with each other and leave X_i alone.
    • The easiest way to ensure this is to just have no occurrences of 2^b at all.
      After all, you can always split a 2^b into two copies of 2^{b-1}.
  • If X_i doesn’t have bit b set, we’d like to set it ourselves, if possible.
    That means having an odd number of 2^b copies.
    • Once again, the easiest way to ensure this is to keep exactly one copy of 2^b: all the remaining can be split into 2^{b-1} instead.

Note that since we’re going from higher bits to lower, pushing down to 2^{b-1} gives us more options for the future.

This “push downwards” idea can be implemented relatively simply by simply keeping a counter denoting the count of the current power of 2.
Let \text{ct} be this count, initially 0. Then, for each b,

  • If A_i has bit b set, increase \text{ct} by 1.
  • Now, if X_i has bit b set, we don’t want any 2^b, and want to make them all 2^{b-1}.
    This can be represented by the transformation \text{ct} \to 2\cdot \text{ct}.
  • If X_i doesn’t have b set, we have two cases.
    • If \text{ct} = 0, we can’t do anything, so just move on.
    • Otherwise, one copy of 2^b remains, and everything else is pushed to 2^{b-1}.
      That is, \text{ct} \to (2\cdot\text{ct})-2.

The only time this “keep for later” idea fails is for b = 0, in which case there isn’t any ‘later’.
So, here alone, we must use every copy of 2^0 we have - meaning the last bit will be decided by parity.
However, notice that any copies pushed down from higher bits don’t actually matter, since they’ll form an even number of copies!

TIME COMPLEXITY:

\mathcal{O}(60\cdot N) per testcase.

CODE:

Author's code (C++)
#include "bits/stdc++.h"
using namespace std;

#define int long long
int sum = 0;

void solve(){
    int n;
    cin >> n;
    assert(n >= 1 and n <= 2e5);
    int a[n], x = 0;
    for (int i = 0; i < n; i++) cin >> a[i], x ^= a[i];
    sum += n;
    int t = 0;
    for (int i = 60; i >= 1; i--) {
        int cnt = 0;
        for (int j = 0; j < n; j++) cnt += (a[j] >> i) & 1;
        if (cnt % 2 == 0 and cnt) t = 1;
        if (t) x |= 1LL << i;
    }
    cout << x << "\n";
}

signed main(){
    ios_base::sync_with_stdio(0); cin.tie(0); cout.tie(0);
    
    int T = 1;
    cin >> T;
    assert(T >= 1 and T <= 1000);
    while(T--) solve();
    assert(sum <= 2e5);

    return 0;
}
Tester's code (C++)
#include <bits/stdc++.h>
using namespace std;
#define int long long

int32_t main() {
    int t;
    cin>>t;
    while(t--)
    {
        int n;
        cin>>n;
        int co[60], ans=0;
        memset(co, 0, sizeof(co));
        for(int i=0;i<n;i++)
        {
            int a;
            cin>>a;
            ans^=a;
            for(int j=0;j<60;j++)
            co[j]+=((a >> j)&1);
        }
        int f=0;
        for(int i=59;i>0;i--)
        {
            if(co[i]>1 && co[i]%2==0)
            f=1;
            if(f)
            ans|=(1ll << i);
        }
        cout<<ans<<"\n";
    }
}
Editorialist's code (Python)
for _ in range(int(input())):
    n = int(input())
    a = list(map(int, input().split()))
    ans, full = 0, 0
    for i in range(n): full ^= a[i]
    ans = full
    for i in range(n):
        rem = full ^ a[i]
        have, cur = 0, 0
        for b in reversed(range(1, 60)):
            if a[i] & 2**b: have += 1
            if rem & 2**b == 0:
                if have > 0:
                    have -= 1
                    cur += 2**b
            else:
                cur += 2**b
            have *= 2
        cur += rem%2 ^ a[i]%2
        ans = max(ans, cur)
    print(ans)
3 Likes

We can simplify the solution to following :

  • x=XOR(A[i]) for all i {1,…N}
  • x=max(x, x | (1<<(log2( (x^A[i]) & A[i])+1) - 2))

Explanation:

Assume we can remove kth bit(0-indexed) then any bit lower [1,k-1] can be set if required.

Why? → If we remove 2^k we can add 2 times 2^(k-1) :

  1. if required to set k-1 th bit we can remove one of 2^(k-1) with 2 time 2^(k-2).
  2. if not required to set i.e. k-1th bit is already set then remove both 2^(k-1) with 4 time 2^(k-2).

keep doing same till k=0. (0th bit can not be replaced and it will always be added even times.)

Thus removing kth bit guarantees all lower bit to be set except 0th.
Hence for each A[i]:

  1. find first bit removing which is beneficial: z=1ll<<log2((x^A[i]) & A[i]). ( use 64 - __builtin_clzll() -1 as log2)
  2. set all bits lesser than z i.e. x|z-1 but except 0th : x=max(x, x|z-2)
2 Likes

Is there any method to solve this problem using tries?

Can anyone tell me where i am doing wrong-#include<bits/stdc++.h>
include
using namespace std;

using ll = long long int;

define yes std::cout << “YES” << std::endl;
define no std::cout << “NO” << std::endl;

define v(n)
std::vector v(n);
for (int i = 0; i < n; ++i) {
std::cin >> v[i];
}

void solve(){
int n;
cin >> n;
v(n);
vectorcnt(61,0);
for(int i=60;i>=0;i–){
for(int j=0;j<n;j++){
if((v[j] & (1ll<<i)) != 0) cnt[i]++;
}
}
for(int i=60;i>=1;i–){
ll val=0;
if(cnt[i] > 0 && cnt[i]%2 == 0){
val = (1ll << i);
cnt[i]–;
cnt[i-1] |= val;
}
}
ll ans=0;
for(int i=60;i>=0;i–){
if(cnt[i]%2){
ans |= (1ll << i);
}
}
cout<<ans<<endl;
}

int main() {
int t;
cin >> t;
while(t–){
solve();
}

return 0;

}

1 Like

I tried a simpler approach which passes some of the test cases but not all. Any explanation as to why this is not the right approach would be appreciated.
A frequency array freq stores, for each bit (from 0 to 59), the number of elements that have the bit set. freq is iterated on from 59 (most significant) to 0. For jth bit, if freq[j] > 1, then an element with the bit set can be broken down into powers of 2 smaller than j appropriately so that total XOR has all bits from 1 to j-1 set. j itself also can be set using other element(s) that have it set. Since 0th power of 2 cant have its parity changed, that will be retained in the answer.
This is my implementation of the approach.

@bpk19 You can figure it out :wink:
Use following test case:
N=3 A={8,8,8}
Answer=8

Ah got it. Thanks for the pointer.