KickStart 19G The Equation

19G The Equation

题目大意:有N个非负数A1 A2… AN,要找到最大的k使得
f ( k ) = ∑ i = 1 N A i   x o r   k ≤ M f(k) = \sum_{i=1}^{N}A_i\space xor\space k \le M f(k)=i=1NAi xor kM

  • 1 ≤ N ≤ 1000.
  • 0 ≤ M ≤ 1015.
  • 0 ≤ Ai ≤ 1015, for all i.

将二进制数的位从低往高编号,依次为bit 0, 1… 。1015 < 250,所以任意Ai的bit 50及更高都是0,k必须< 250

设nums[i] = bit i为1的Aj的个数,k的bit i为Ki,如果Ki = 1,那bit i贡献(N - nums[i]) * 2i;如果Ki = 0,那bit i贡献nums[i] * 2i。f(k)就是50位的贡献总和。

问题变成,现在有一个2×50的非负数矩阵Mat,行号0-1,列号0-49。令sum=0,k=0,然后每列都二选一,假设列j选择了Mat[i][j],sum加上该值,并把k的bit j设为i。求出最大的k使得sum <= M。

用贪心法解决这个问题。要使得k尽量大,那高位要尽可能放1。我们从高往低看,即按列号递减的顺序看。当到达列j,先尝试放1,如果这里选1,且更低的列都选两个里面更小的那个,加起来得到的sum <= M,说明可以选1达到最优。如果不能选1,接着尝试0,还不能的话说明不存在符合的k。

#include <iostream>
#include <vector>
#include <unordered_map>
#include <string>
#include <algorithm>

using namespace std;
using ll = long long;
const ll MAXM = 1e15; // 1e15 < 2^50
const int B = 50;

class Solution {
public:
    void solve() {
        int N; // 1 <= N <= 1000
        ll M; // 0 <= M <= MAXM
        cin >> N >> M;
        vector<int> nums(B); // nums[i] 第i位为1的Aj的个数
        for(int i = 0; i < N; i++) {
            ll x; // 0 <= x <= MAXM
            cin >> x;
            for(int j = 0; j < B; j++) {
                if(x & (1ll << j)) nums[j]++;
            }
        }
        vector<vector<ll>> values(B, vector<ll>(2));
        for(int i = 0; i < B; i++) {
            ll coef = 1ll << i;
            values[i][1] = coef * (N - nums[i]);
            values[i][0] = coef * nums[i];
        }
        vector<ll> minsum(B);
        for(ll i = 0, x=0; i < B; i++) {
            x = minsum[i] = x + min(values[i][0], values[i][1]);
        }
        ll res = 0, sum = 0;
        for(int i = B-1; i >= 0; i--) {
            bool ok = false;
            for(int j = 1; j >= 0; j--) {
                if((i == 0 && sum + values[i][j] <= M) || (i > 0) && (sum + values[i][j] + minsum[i-1] <= M)) {
                    sum += values[i][j];
                    if(j == 1) res |= (1ll << i);
                    ok = true;
                    break;
                }
            }
            if(!ok) {
                res = -1; break;
            }
        }
        cout << res << endl;
    }
};

int main() {
    int T;
    cin >> T;
    auto sln = new Solution();
    for (int i = 1; i <= T; i++)
    {
        cout << "Case #" << i << ": ";
        sln->solve();
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值