51nod 區間的價值 V2

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;
}


發佈了31 篇原創文章 · 獲贊 0 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章