小K的区间与值和
问题描述
小K有一个数组,她定义数组的权值为数组中任选两个数的按位与的值之和。具体来说,对于数组中的每个连续子数组,我们可以计算所有可能的两个元素的按位与值之和,并将这些值相加。小K想知道该数组中所有可能的连续子数组的权值和是多少,最后结果对取模。
测试样例
样例1: 输入:n=4,a=[2,3,1,2] 输出:16
样例2: 输入:n=3,a=[5,6,7] 输出:25
样例3: 输入:n=2,a=[1,10] 输出:0
样例4: 输入:n=5,a=[1,2,4,8,16] 输出:0
题目分析
一、暴力做法
我们可以枚举所有存在的子数组,然后对于每一个子数组,都去选择两个数,相与并将答案计入。
const long long mod = 1e9+7;
int solution(int n, vector<int> a) {
vector<long long> res(n+3,0);
long long ans = 0;
for(int i=0;i<n;i++) {
for(int j=0;j<i;j++) {
for(int r=j;r<=i;r++) {
for(int l=j;l<r;l++) {
ans = (ans + (a[l] & a[r])) % mod;
}
}
}
}
return ans;
}
这虽然也能过,但我们还是要去追求更快更好的解决方法
二、枚举每一对数字相与的贡献
对于一对数字 a[i] 和 a[j](其中 i < j),我们去计算共有多少个子数组包含这对数字,可以发现包含这两个数的子数组的数量等于(i+1) * (n-j)。
const long long mod = 1e9+7;
int solution(int n, vector<int> a) {
vector<long long> res(n+3,0);
long long ans = 0;
for(int i=0;i<n;i++) {
for(int j=0;j<i;j++) {
ans = (ans + (j+1) * (n-i) * (a[i] & a[j])) % mod;
}
}
return ans;
}
但还是不够快,我们还能更进一步!
三、计算每一个二进制位的贡献
题目中有一个很重要的信息我们并没有使用到:两个元素的按位与值之和
对于位运算题目,我们完全可以独立考虑每一个二进制位对答案的影响,不同位之间并无关系。这样我们就将问题转化为了01序列中所有子序列里的1的贡献。
我们从左往右扫描,计算第i个1作为区间右端点时的贡献,也就是把前面所有1出现的位置再+1的和。
const ll mod = 1e9+7;
int solution(int n, vector<int> a) {
vector<ll> res(n+3,0);
ll ans = 0;
for(int base=0;base<=30;base++) {
ll t = 1ll << base;
ll cnt = 0;
for(int i=1;i<=n;i++) {
res[i] = a[i-1] >> base & 1;
if(res[i] == 1) {
ans = (ans + cnt * (n-i+1) % mod * t) % mod;
cnt += i;
}
}
}
return ans;
}
得到了一个的算法,这里的V是指数组a值域上限。