【题解】CF1151C Problem for Nazar(二分答案)
【题解】CF1151C Problem for Nazar
距离 CSP 剩下 10 天了,据说考前写题解可以增加 RP所以我来写一篇题解+水点贡献分
看题解区没有用二分答案来解决这道题的,我来提供一个二分做法。
题目链接
题意概述
设正奇数集合为 ,正偶数集合为 ,这两个集合是无限集。
在黑板上写了无数轮数,第 轮写下了 个数.
当为奇数时,从集合 中向后取数,当 为偶数时,从集合 中向后取数。
求黑板上第 个数到第 个数的和,模 ()
数据范围
思路分析
首先看到这个数据范围,可以考虑到时间复杂度大概率是 这个级别的。
求的是下标为区间 的和,那我们很自然联想到差分:可以先把区间 的和求出来减去区间 的和。
那么问题就转化为怎么求出前 个数的和。
观察题目条件可以发现,黑板上的数无非就是一串奇数+一串偶数+一串奇数……这样排列的。
且相邻的两串奇数之间一定是连续的,也就是假如这一串奇数的结尾是 ,那么与它相邻的下一串奇数的第一个数一定是 。
相邻的两串偶数之间也同理。
注意:这里的相邻的两串奇数与相邻的两串数含义不同,每相邻的两串奇数之间隔着一串偶数。
那么我们能否把前 个数的和转化为所有的奇数串之和加上所有的偶数串之和呢?
答案是肯定的。
考虑最终形成的序列一定是这样的:
个奇数+ 个偶数+ 个奇数+ 个偶数+ + 个不知道是奇数还是偶数的一串数+某个数 到 。
很显然第 串数的奇偶性取决于 的奇偶性,并与 的奇偶性相同。
这里的 是第 串数的最后一个数 +2。
那么我们其实可以考虑把前 串数的和求出来,然后再单独把从 到 的和求出来然后相加。
那么我们首先要求出这个 。
因为前 个串的长度和为 ,所以我们可以对于每个 直接二分求出这个 是多少。
具体代码如下:
int now=0,ret=0;
for(int step=(1ll<<60);step>=1;step>>=1)
{
if(now+step<=60&&(1ll<<(now+step))-1<x)now+=step;
}
(这里采用的是倍增二分)
最后得到的这个 就是我们所说的 。
由于前面已经提到过,相邻的两串奇数/偶数之间都是连续的。
那我们可以直接将前 串数的和分成奇数列和偶数列来求。
显然,对于前 串数,有 串偶数列,有 串是奇数列。
那么我们可以预处理出来奇数列和偶数列单独的和。
用 表示整个序列中前 串奇数列中有多少个数, 表示整个序列中前 串偶数列中有多少个数,预处理如下:
void init()
{
int cnt1=0,cnt2=0;
for(int i=1;i<=60;i++)//之所以是 60,因为 2^60-1 已经大于 1e18 了,所以最多有 60 串数。
{
if(i&1)
{
cnt1++;
sum1[cnt1]=sum1[cnt1-1]+(1ll<<(i-1));
}
else
{
cnt2++;
sum2[cnt2]=sum2[cnt2-1]+(1ll<<(i-1));
}
}
}
那么对于前 串数,有 个偶数, 个奇数。
接下来要利用一些等差数列的知识:
-
等差数列第 项为 ( 表示首项, 表示公差)。
-
等差数列前 项的和为 。
那么第 个偶数就为 ,第 个奇数就为 。
所以前 串数中偶数列之和为 ,奇数列之和为
然后对于最后 到 的那一段和,可以先把 求出来:当 为奇数时,,当 为偶数时,。然后再把 求出来:当 为奇数时,,当 为偶数时,。
然后从 到 也可以直接等差数列求和,设结果为 。
那么最后答案就是 。
易错点
别忘了及时取模以及负数保护!!!防止爆 long long!我在这挂了 2h……老毛病又犯了。。
代码实现
//CF1151C
#include<cstdio>
#include<iostream>
#define int long long
//#define debug
using namespace std;
const int maxn=105;
const int mod=1e9+7;
int sum1[maxn],sum2[maxn],n;
inline int read()
{
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
return x*f;
}
void init()
{
int cnt1=0,cnt2=0;
for(int i=1;i<=60;i++)
{
if(i&1)
{
cnt1++;
sum1[cnt1]=sum1[cnt1-1]+(1ll<<(i-1));
}
else
{
cnt2++;
sum2[cnt2]=sum2[cnt2-1]+(1ll<<(i-1));
}
}
}
int work(int x)
{
int now=0,ret=0;
for(int step=(1ll<<60);step>=1;step>>=1)
{
if(now+step<=60&&(1ll<<(now+step))-1<x)now+=step;
}
int L=now;
int re=x-(1ll<<L)+1;
int tt=L>>1;
int num=sum2[tt];
int tot1=num*2;
int s1=(2+tot1)/2%mod*(num%mod)%mod;
int kk=L-tt;
num=sum1[kk];
int tot2=num*2-1;
int s2=(1+tot2)/2%mod*(num%mod)%mod;
if(L&1)
{
(ret=(s1+s2)%mod+(tot1+2+tot1+re*2)/2%mod*(re%mod)%mod)%=mod;
}
else
{
(ret=(s1+s2)%mod+(tot2+2+tot2+re*2)/2%mod*(re%mod)%mod)%=mod;
}
return ret;
}
signed main()
{
init();
int l,r;
l=read();r=read();
cout<<(work(r)-work(l-1)+mod)%mod<<'\n';
return 0;
}