题目链接

Poi 的新加法(Easy Version)

题目描述

本题为问题的简单版本。定义一种新的加法运算 ,其运算规则等价于 。给定一个长度为 的序列 (其中 ),求解 的值。

解题思路

首先,我们需要确定 的确切定义。题目描述中虽然给出了 的公式,但这实际上等价于 。通过题目给出的二进制运算表示例可以确认,我们应该使用后者进行计算。

本题是简单版本,查询固定为整个区间 。我们需要计算 res = a[1]; for i=2..n: res = (res & a[i]) << 1

很大时,直接模拟的效率可能不够。我们需要找到一个更快的算法。

关键观察与优化

让我们分析这个迭代运算 res_new = (res_old & a[i]) << 1 的性质:

  1. 最低有效位 (LSB) 的变化<< 1 操作意味着每次运算后,结果的二进制表示末尾都会增加一个 0。只要中间结果不为零,其 LSB 的位置就会严格加一。经过 次运算后,结果的 LSB 至少在第 位(从0开始计数),即这个数一定是 的倍数。

  2. 最高有效位 (MSB) 的限制:题目中给出 。这意味着 res_old & a[i] 的结果不会超过 ,其 MSB 不会超过第 59 位。经过 << 1 操作后,res_new 的 MSB 最多在第 60 位。因此,运算过程中的任何中间结果和最终结果,其最高有效位都不会超过第60位

推论

对于区间 的计算,总共需要进行 次运算。如果最终结果非零,它的 LSB 至少在第 位,而 MSB 至多在第 60 位。一个非零数必须满足 LSB MSB,因此必须有 ,即

如果 ,那么 LSB 的理论最小位置将会超过 MSB 的理论最大位置,这是不可能的,除非这个数本身就是 0

最终算法

  • 对于给定的序列,我们首先检查其长度
  • 如果 ,我们可以直接断定结果为 0
  • 如果 ,则执行暴力模拟计算。

由于 的最大值为 ,这个剪枝可以极大地优化计算。

代码

#include <iostream>
#include <vector>

using namespace std;

void solve() {
    int n, q;
    cin >> n >> q; // q = 1 for easy version
    vector<unsigned long long> a(n); // 使用 unsigned long long 存储 60 位数据
    for (int i = 0; i < n; ++i) {
        cin >> a[i];
    }
    
    int l, r;
    cin >> l >> r; // l=1, r=n for easy version

    if (n > 61) { // 运算次数为 n-1, 如果 n-1 > 60, 结果必为0
        cout << 0 << "\n";
        return;
    }
    
    unsigned long long current_val = a[0];
    for (int j = 1; j < n; ++j) {
        current_val = (current_val & a[j]) << 1;
    }
    cout << current_val << "\n";
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(NULL);
    int t;
    cin >> t;
    while (t--) {
        solve();
    }
    return 0;
}
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int t = sc.nextInt();
        while (t-- > 0) {
            solve(sc);
        }
    }

    private static void solve(Scanner sc) {
        int n = sc.nextInt();
        int q = sc.nextInt(); // q = 1
        long[] a = new long[n]; // Java long is 64-bit, sufficient for 2^60
        for (int i = 0; i < n; i++) {
            a[i] = sc.nextLong();
        }

        int l = sc.nextInt(); // l = 1
        int r = sc.nextInt(); // r = n

        if (n > 61) { // 运算次数为 n-1, 如果 n-1 > 60, 结果必为0
            System.out.println(0);
            return;
        }

        long currentVal = a[0];
        for (int j = 1; j < n; j++) {
            currentVal = (currentVal & a[j]) << 1;
        }
        System.out.println(currentVal);
    }
}
import sys

def solve():
    n, q = map(int, sys.stdin.readline().split()) # q=1
    a = list(map(int, sys.stdin.readline().split()))
    l, r = map(int, sys.stdin.readline().split()) # l=1, r=n

    # 运算次数为 n-1
    if n > 61: # 如果 n-1 > 60, 结果必为0
        print(0)
        return

    current_val = a[0]
    for i in range(1, n):
        current_val = (current_val & a[i]) << 1
    print(current_val)


t_str = sys.stdin.readline()
if t_str:
    t = int(t_str)
    for _ in range(t):
        solve()

算法及复杂度

  • 算法:位运算 + 剪枝
  • 时间复杂度:。对于每组测试数据,读入 个数需要 ,后续计算是 ,所以总时间由输入规模决定。 是所有测试数据中 的总和。
  • 空间复杂度:,用于存储单个测试用例的序列。