51nod 区间的价值 V2

2 篇文章 0 订阅
1 篇文章 0 订阅
alpq654321  (命题人)
基准时间限制:1 秒 空间限制:131072 KB 分值: 40
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;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值