题意:
给你如下的公式,要求你算出这个表达式的值。
∑ni=1∑nj=i(⌊log2S(i,j)⌋+1)×(i+j)
其中S(i,j)表示 i 到 j 的和。
解析:
由于 (⌊log2S(i,j)⌋+1) 的和表示的是: S(i,j) 这个数字,转化成二进制的长度。
由于虽然连续的和可能比较大,但是其二进制的长度最多不会超过34位。
所以我们可以枚举,其二进制位数,并记为k。
然后查找满足区间 [2k,2k−1] 的连续区间 [l,r]
连续区域 [l,r] 求出后,那么 [l,r] 肯定满足一个等差数列,可以用等差数列的前n想和算出 (l+r−1)∗(r−l)/2 ,然后再加上 (r−l) 个 i ,
那么最后答案就是(∑(l+r−1)∗(r−l)/2+(r−l)∗i)∗k 至于如何算出该区间,可以通过尺取法算出。
如果固定下一个端点i,延长j那么S(i,j)肯定的单调递增的。
可以通过这个单调性,用尺取法来算出这个区间的[l, r]的范围,总复杂度 O(nlogn) 。
my code
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N = 1e5 + 10;
ll sum[N];
int n;
ll solve(ll L, ll R) {
ll ret = 0;
ll l = 0, r = 0;
for(ll i = 1; i <= n; i++) {
l = max(i, l);
r = max(i, r);
while(l <= n && sum[l] - sum[i-1] < L) l++;
while(r <= n && sum[r] - sum[i-1] < R) r++;
if(l >= r) continue;
ret += (r - l) * i + (l + r - 1) * (r - l) / 2;
}
return ret;
}
int main() {
int T;
scanf("%d", &T);
while(T--) {
scanf("%d", &n);
sum[0] = 0;
for(int i = 1; i <= n; i++) {
scanf("%lld", &sum[i]);
sum[i] += sum[i-1];
}
ll ans = 0, L, R;
for(ll k = 1; k <= 34; k++) {
L = 1LL << (k-1);
R = 1LL << k;
ans += k * solve(L, R);
if(L > sum[n]) break;
}
ans += solve(0, 1);
printf("%lld\n", ans);
}
return 0;
}