1151_DIV2:C. Problem for Nazar(数学)

题目大意:有奇数和偶数两个无穷大的集合,奇数集合是{1,3,5,7…},偶数集合是{2,4,6,8…},先从奇数取1个,然后从偶数取2个,再在奇数取4个…,每次取都取前面的数量的两倍,在两个集合交替取,然后将取出来的数构成一个新的无穷大的集合 {1,2,4,3,5,7,9,6,8,10,12…}。
问在新的集合中,第 l 到 第r个的数字的和是多少?(对1e9 + 7取模)

做法:其实是一道数学模拟题,显然答案满足前缀和性质,我们可以用solve( r ) - solve(l - 1) 得到答案。如何解solve(x) 呢?,按题意倍增即可,因为集合中是连续的奇数块和连续的偶数块构成的,块的长度是2的幂次,我们可以把奇数块和偶数块分开,发现相邻的奇数块或偶数块长度相差四倍。
我们从第一个块开始遍历,看到第几个块总长能超过x,然后求一下和就行。
求和的话,因为每一个块都是连续的等差数列,只要算出这个块的第一项是多少,然后根据块的长度和第一项的值可以利用公式O(1)算出来,特殊算一下最后一个块(因为最后一个块没有全部包含),此处注意要各种取模,不然会溢出,能取模的地方都取模。
复杂度log(n)

代码:

#include<bits/stdc++.h>
using namespace std;
const long long mod = 1e9 + 7;
long long l,r;
long long fpow(long long a,long long b) {
	long long res = 1;
	while(b) {
		if(b & 1) res = res * a;
		a = a * a;
		b >>= 1;
	}
	return res;
}
long long solve(long long l) {
	long long odd = 0,even = 0; //记录前面有几个奇数块/偶数块,用来算当前块的第一个数字
	long long cnt = 0,i;		//cnt 是当前块,i是当前块的长度
	long long ans = 0;			//记录和
	for(i = 1,cnt = 1,even = 0,odd = 0; i <= l; i += i,cnt++){
		if(cnt % 2 == 0) {
			long long tmp = 2 * (fpow(4,even) - 1) / 3;
			//a1 = 2 + 2 * tmp;
			ans = ((ans + ((2 + 2 * tmp) % mod) * (i % mod)) % mod + ((i - 1) % mod * (i % mod)) % mod ) % mod;
			even++;
		}
		else {
			long long tmp = 1 * (fpow(4,odd) - 1) / 3;
			//a1 = 1 + (tmp) * 2
			ans = ((ans + ((1 + 2 * tmp) % mod) * (i % mod)) % mod  + ((i - 1) % mod * (i % mod)) % mod) % mod; 
			odd++;
		}
		l -= i;
	}	
	if(l) {
		if(cnt % 2 == 0) {
				long long tmp = 2 * (fpow(4,even) - 1) / 3;
				//a1 = 2 + 2 * tmp;
				ans = ((ans + ((2 + 2 * tmp) % mod) * (l % mod)) % mod  + ((l - 1) % mod * (l % mod)) % mod ) % mod;
				even++;
		}
		else {
				long long tmp = 1 * (fpow(4,odd) - 1) / 3;
				//a1 = 1 + (tmp) * 2
				ans = ((ans + ((1 + 2 * tmp) % mod) * (l % mod)) % mod  + ((l - 1) % mod * (l % mod)) % mod ) % mod; 
				odd++;
			}
	}
	return ans % mod;
}
int main() {
	scanf("%lld%lld",&l,&r);
	printf("%lld\n",(solve(r) + mod - solve(l - 1)) % mod);
	return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_41997978/article/details/89534553