lyk拥有一个区间。
它规定一个区间的价值为这个区间中所有数and起来的值与这个区间所有数or起来的值的乘积。
例如3个数2,3,6。它们and起来的值为2,or起来的值为7,这个区间对答案的贡献为2*7=14。
现在lyk有一个n个数的序列,它想知道所有n*(n+1)/2个区间的贡献的和对1000000007取模后的结果是多少。
例如当这个序列为{3,4,5}时,那么区间[1,1],[1,2],[1,3],[2,2],[2,3],[3,3]的贡献分别为9,0,0,16,20,25。
Input
第一行一个数n(1<=n<=100000)。 接下来一行n个数ai,表示这n个数(0<=ai<=10^9)。
Output
一行表示答案。
Input示例
3 3 4 5
Output示例
70
简单说一下我的做法:
对于这道题我是仿照 hdu5869 的做法来做的,即枚举区间右边界,然后查询区间不同的 '与和' 和 '或和'。
很容易发现对于一个固定的右边界的区间,在往左 与 的过程中得到的值是非增的,而且不同的值最多也就 log(a[R]) 个,因为每一次 与 数值要么不变,要么就是下降2的倍数倍。对于 或 操作来说也是类似的情况。
因此如果我们可以迅速知道一个区间的'与和'和'或和',就可以在遍历区间的过程中跳过一些数值相同的区间来达到优化的效果。
那么关键就在于如何快速知道一个区间的'与和'和'或和'。以与操作为例,可以把数写成二进制形式,然后只看单个位,比如说最低位,可以发现,当这一位,往左与的过程中,必定会在某一位置之后就会固定不变,比如说 0 在往左与的过程中如果遇到一个 1 ,这位之后再进行与操作也会一直都是 1。那么根据这一性质,对于 0 我们可以记录左边的数中相同位离它的 1 的位置,对于 1 记录左边的数中相同位离它最近的 0。预处理出这些信息后我们就可以推出 区间与 和 区间或 的结果的每一个位到底是0还是1,从而得到区间与 和 区间或 的结果。
#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N = 100100;
const int M = 1000000007;
int LeftRev[32][N];
bool bit[N][32];
int main()
{
int n;
int HighestDigit = 0;
ll x;
scanf("%d", &n);
for(int i = 1; i <= n; i++)
{
scanf("%lld", &x);
int cnt = 0;
while(x)
{
if(x & 1) bit[i][cnt] = true;
x >>= 1;
cnt++;
}
HighestDigit = max(HighestDigit, cnt);
}
for(int i = 0; i < HighestDigit; i++)
{
int one = 0, zero = 0;
for(int j = 1; j <= n; j++)
{
if(bit[j][i])
{
LeftRev[i][j] = zero;
one = j;
}
else
{
LeftRev[i][j] = one;
zero = j;
}
}
}
ll ans = 0;
for(int R = 1; R <= n; R++)
{
int L = R;
int pre = R;
while(L > 0)
{
ll AndSum = 0, OrSum = 0;
int next = 0;
for(int i = 0; i < HighestDigit; i++)
{
if(bit[R][i])
{
if(L > LeftRev[i][R]) AndSum |= 1LL << i;
OrSum |= 1LL << i;
}
else
{
if(L <= LeftRev[i][R]) OrSum |= 1LL << i;
// AndSum's ith digit is zero
}
if(LeftRev[i][R] < L)
next = max(next, LeftRev[i][R]);
}
L = next;
//cout << L + 1 << ' ' << R << ' ' << AndSum << ' ' << OrSum << endl;
//system("pause");
ans = (ans + (((AndSum * OrSum) % M) * (pre - L)) % M) % M;
pre = L;
}
}
printf("%lld\n", ans);
return 0;
}