【问题标题】:Increase set of numbers so that XOR sum is 0增加一组数字,使 XOR 和为 0
【发布时间】:2012-12-21 09:03:28
【问题描述】:

我需要一些帮助来解决我已简化为以下问题的问题。我有 N 个 30 位数,因此它们的组合 XOR 是非零的。我需要为 N 个数字中的每一个添加一个非负(0 或更多)值,使得新数字的组合 XOR 变为 0,在总相加值(而不是相加数)最小化的约束下.

例如,如果我将数字 (01010)2、(01011)2 和 (01100)2 作为三个数字 (N = 3)。那么,它们的 XOR 组合为 (01101)2。我们可以添加一些数字如下:

  • (01010)2 + (00001)2 = (01011)2 : (+1)
  • (01011)2 + (10000)2 = (11011)2 : (+16)
  • (01100)2 + (00100)2 = (10000)2 : (+4)

现在,新数字的总异或是 0,总加法是 21 (=+1+16+4)。这个总附加值必须最小化(可能有一个更好的分布来减少这个总数,但这只是一个示例)。

这些数字每个都是 30 位,所以这些数字可能很大,并且 N

谢谢!

【问题讨论】:

  • 对于上面的例子,有什么原因你不能只在其中一个数字上加上 21 吗? (也许是不允许模环绕?)
  • 第一个数加21只得到11111、01011、01100这三个数的异或和不为0。异或和也应该为0。除了问题中提到的两个约束(XOR 变为 0,总加法最小化)之外,没有其他环绕或其他问题。
  • 对不起 - 我应该说:有什么理由不能只为其中一个数字添加适当的值以获得所需的结果?我不明白为什么需要为多个元素添加值?
  • @EvgenyKluev 这确实是重复的,但那里接受的解决方案确实不处理最小条件。我希望得到一个更完整的答案。

标签: algorithm optimization bit-manipulation dynamic-programming xor


【解决方案1】:

好问题:)

我想出了一种在 O(n * 2^n * 31 * n) 中运行的方法,对于 n = 15,对于一个测试用例来说它有点慢 (228556800)。以下是详细信息:

我这里使用的是dp方法(memoization),我们定义一个状态为(int mask, int pos):

  • 面具

    0 0,则表示之前已经添加了数字 i,所有低位 (

  • 位置

    当前校验位位置,从高到低开始

我们从最高位到最低位开始,每次检查给定数字的当前位设置的计数,我们将其表示为one_cnt,如果

  • one_cnt 是偶数

    当前 pos 的异或为零,我们只是移动到 (mask, pos - 1)

  • one_cnt 是奇数

    如果 one_cnt 等于 n(全奇数),这里我们认为是一个坏状态,什么也不做。否则我们迭代在 pos 处包含零的数字并尝试在此处放置一个。

请注意,当 one_cnt 完全为奇数时,我们将其视为不良状态,因为我们不想增加到可能影响先前状态的 (pos + 1)(违反 dp 原则)。

但是会有这样的情况:arr = [1, 1, 1] 并且解决方案存在。所以在这里我们尝试做一些额外的计算:

我们从最高位 pos 开始,检查当前位是否包含甚至一位,如果是,我们迭代数字以将 1 设置为当前 pos 中为零的数字,然后我们开始我们的记忆并更新我们的结果。

例如如果 arr = [1, 1, 1],我们可以检查 [2, 1, 1], [1,2,1], [1,1,2]

希望我已经解释清楚了。

如果我想出更快的方法,我会更新解决方案:)

代码如下:

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <string>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <vector>
#include <set>
#include <map>
#include <ctime>
#include <cassert>

using namespace std;

#define fs first
#define sc second
#define pb push_back
#define mp make_pair
#define range(i, n) for (long long i=0; i<(n); ++i)
#define forit(it,v) for(typeof((v).begin()) it = v.begin() ; it != (v).end() ; ++it)
#define eprintf(...) fprintf(stderr, __VA_ARGS__),fflush(stderr)
#define sz(a) ((int)(a).size())
#define all(a) (a).begin(),a.end()
#define two(i) (1LL<<(i))

typedef long long ll;
typedef vector<int> VI;
typedef pair<int, int> PII;

int n;
vector<ll>  arr;
ll ans;
map<PII, ll> M;

void update(ll & ret, ll tmp) {
    if (tmp == -1) return;
    if (ret == -1) ret = tmp;
    ret = min(ret, tmp);
}

/*
 * memoization(mask, pos)
 * Args:
 * mask: if 2^i in mask it means arr[i] has been added a high bit before, and all lower bit(<=pos) can be considerd zero.
 * pos: current check bit position, start from high to low
 * Return:
 *  return -1 if not valid ans exists else return minimum addition sum 
 */
int memoization(int mask, int pos) {

    if (pos < 0) {
        return 0;
    }

    PII state = mp(mask, pos);
    if (M.find(state) != M.end()) {
        return M[state];
    }

    ll &ret = M[state];
    ret = -1;

    int one_cnt = 0;
    for (int i = 0; i < n; i++) {
        if ( !(mask & two(i)) && 
                (two(pos) & arr[i])) {
            one_cnt ++;
        }
    }

    if (one_cnt % 2 == 0) { // even, xor on this pos equals zero
        ret = memoization(mask, pos - 1);
    } else {
        if (one_cnt == n)  { //full odd  bad state, do nothing
            //pass
        } else { //not full odd, choose one empty bit  to place 1  
            for (int i = 0; i < n; i++) {
                if ((mask & two(i))  //if number i has been added before, then it contain zero at pos 
                        || !(two(pos) & arr[i])  // or if number i has zero at pos and hasn't been added before
                        ) {
                    ll candi = memoization(mask | two(i), pos - 1);
                    ll added = mask & two(i) ? two(pos)  // number i has been added before, so we need extra two(pos) sum
                        //number i hasn't been added before, we need calc the new sum 
                        //here we only consider bits in [0 .. pos]
                        : two(pos) - arr[i] % two(pos + 1); 
                    if (candi >= 0)  // legal result
                        update(ret,  candi + added);
                }
            }
        }
    }

    return ret;
}

int main() {
#ifndef ONLINE_JUDGE
    freopen("g.in", "r", stdin);
#endif
    while (cin >> n) {
        arr.clear();
        for (int i = 0; i < n; i++) {
            ll val;
            cin >> val;
            arr.push_back(val);
        }

        ll max_val = arr[0];
        for (int i = 1; i < n; i++) max_val = max(max_val, arr[i]);

        int max_pos = 0;
        while (max_val) max_pos ++, max_val >>= 1;
        max_pos ++;

        //no adjust
        M.clear();
        ans = memoization(0, 31);

        bool even_bit = true;
        for (int i = max_pos; i >= 0; i--) {
            int one_cnt = 0;

            for (int j = 0; j < n; j++) one_cnt += (two(i) & arr[j]) > 0;
            even_bit &= one_cnt % 2 == 0;

            if (even_bit) {
                for (int j = 0; j < n; j++) {
                    //arr[j] at pos i is empty, try add to 1
                    if (!(two(i) & arr[j])) {
                        ll backup = arr[j];
                        arr[j] = two(i);

                        //since previous pos all contain even one bits, we just start from current pos i
                        M.clear();
                        ll candi = memoization(0, i);
                        ll added = two(i) - backup % two(i);
                        if (candi >= 0) 
                            update(ans, candi + added);

                        arr[j] = backup;
                    }
                }
            }
        }
        cout << ans << endl;
    }

    return 0;
}

【讨论】:

    【解决方案2】:

    算法:

    找到 k,即给定数字(在您的示例中为 4)的异或和的最高有效位的位置。确定是否所有给定的数字都设置了给定的位(如您的示例中所示)。

    如果他们这样做,那么您必须增加两个给定数字,这样它们的最高有效位将位于位置 k+1。要确定女巫,您应该强制所有数字对并增加其中一个直到它变为 2^(k+1) 和另一个直到 xor-sum 等于 0。然后选择最佳对。

    如果他们不这样做,那么你只需要增加一个给定的数字,它的第 k 位为 0。要确定女巫,你应该暴力破解所有这些数字并增加它们直到 xor-sum等于0。然后选择最好的。

    要确定其中一个数字应增加多少以使所有数字的异或和变为 0,请计算所有其他数字的异或和并从中减去必须增加的数字。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-08-29
      • 1970-01-01
      • 2022-01-03
      • 2018-07-25
      • 1970-01-01
      相关资源
      最近更新 更多