题意 :
- 原序列长为n,给m个连续子序列的左右端点的下标以及该连续子序列的或和,保证这m个连续子序列的并集包含原序列每一个点,求原序列的所有子序列的异或的和,输出任意一种
思路 :
- 由于位运算对每个位都是独立的,考虑分别对每一位求解
- 首先看输入的给我们带来什么,对于[l,r]得到的同或和x,考虑它的某一位,如果这一位是0,说明[l,r]每个元素这位都是0,如果这一位是1,至少有1个1,后者没什么用
- 考虑某一位bit,一共有n个元素,假设有a个1,b个0,显然有 a + b = n a+b=n a+b=n
- 我们如果要对结果(所有子序列异或和的总和),就要选出若干个元素使它们异或和为1,根据异或和的性质,需要选择
奇数个1和若干个(0)0
- 选奇数个1的方案为 C a 1 + C a 3 + . . . = 2 a − 1 C_{a}^{1}+C_{a}^{3}+... =2^{a-1} Ca1+Ca3+...=2a−1,选任意个0的方案为 2 b 2^{b} 2b
- 根据乘法原理,总方案数为 2 a − 1 ∗ 2 b = 2 a + b − 1 = 2 n − 1 2^{a-1}*2^b=2^{a+b-1}=2^{n-1} 2a−1∗2b=2a+b−1=2n−1,在当前bit位上贡献为 1 < < b i t 1<<bit 1<<bit
- 所以对于每个位,只要有1,那么这个位对结果的贡献就是 2 n − 1 ∗ ( 1 < < b i t ) 2^{n-1}*(1 << bit) 2n−1∗(1<<bit),否则这个位对结果的贡献是0
- 假设bit位存在1,那么v中就可以拆除一块(1 << bit),我们按照这种方法把v拆成若干个块,每个块都能产生这么多贡献,因此可以直接用v乘上 2 n − 1 2^{n-1} 2n−1得到答案
- 即,只要看哪些位有1,
(
1
<
<
b
i
t
)
∗
2
n
−
1
(1<<bit)*2^{n-1}
(1<<bit)∗2n−1即可,事实上,设置一个初始变量v为00…0,每次读入一个区间,都能知道哪些位现在至少有一个1,这个位可以被累加答案了,
v|=x
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#include <unordered_set>
#include <math.h>
#define endl '\n'
#define fi first
#define se second
#define pb push_back
using namespace std;
using ll = long long;
typedef pair<int, int> PII;
const ll mod = 1e9 + 7;
void solve()
{
int n, m, v = 0; cin >> n >> m;
for (int i = 1, l, r, x; i <= m && cin >> l >> r >> x; i ++ ) v |= x;
ll fac = 1;
for (int i = 1; i <= n - 1; i ++ ) fac = fac * 2ll % mod;
cout << fac * v % mod << endl;
}
int main()
{
cin.tie(nullptr) -> sync_with_stdio(false);
int _;
cin >> _;
while (_ -- )
solve();
return 0;
}