题目链接:
https://vjudge.net/contest/416227#problem/C
这个题要求(sum_{i=0}^{X}sum_{j=[i==0]}^{Y}[i&j==0]lfloorlog_{2}(i+j)+1 floor)
(0le X,Yle1e9)
然后有(T)组数据,(Tle 1e5),限时(1s)
从对([i&j==0]lfloorlog_{2}(i+j)+1 floor)求和这一要求可以看出,我们只考虑(i&j==0)的情况,它意味着(i,j)的二进制位不可同为(1),等价于(i+j)不会发生任何进位。(lfloorlog_{2}(i+j)+1 floor)是(i+j)的二进制位数,由于(i+j)不会发生任何进位,(lfloorlog_{2}(i+j)+1 floor)就是(i,j)的位数的最大值。
当(i)取([2^a,2^{a+1}-1]),(j)取([2^a,2^{a+1}-1])时,这个计数问题很容易解决,即为(2cdot 3^a(a+1))
解释:
第(a)位(从小到大数,且个位为第(0)位)上,要么(i)的第(a)位是(1),(j)的第(a)位是(0),要么(i)的第(a)位是(0),(j)的第(a)位是(1)。然后第(0)到第(a-1)位,每一位有(3)种可能:
(i)对应(0),(j)对应(0)
(i)对应(0),(j)对应(1)
(i)对应(1),(j)对应(0)
(i+j)的位数即(i,j)的位数的最大值(a+1),故答案即为(2cdot 3^a(a+1))
在最理想的情况下,求和是很简单的,我们可以稍微把问题变得复杂一些:
当(i)取([N,N+2^{a}-1]),(j)取([M,M+2^{b}-1])
而且(N&(2^{a}-1)==0,M&(2^{b}-1)==0,Nge 2^a,Mge 2^b)
即(N)的最小的(a)位都是(0),(M)的最小的(b)位都是(0),(N)的位数大于(a),(M)的位数大于(b)
此时(i)的位数等于(N)的位数,(j)的位数等于(M)的位数,那么满足(i&j==0)的(lfloorlog_{2}(i+j)+1 floor)就是(N)的位数和(M)的位数的最大值,记为(k),这显然是一个定值
(sum_{i=N}^{N+2^a-1}sum_{j=M}^{M+2^b-1}[i&j==0]lfloorlog_{2}(i+j)+1 floor=ksum_{i=N}^{N+2^a-1}sum_{j=M}^{M+2^b-1}[i&j==0])
由于这个求和是可以交换(i,j)的,我们不妨设(age b)
这样,在最小的(b)位中,(i,j)的取值是自由的,每一位对应(3)种对求和有意义的取值组合:
(i)对应(0),(j)对应(0)
(i)对应(0),(j)对应(1)
(i)对应(1),(j)对应(0)
有(3^b)种可能
假如(a>b),那么从第(b)到第(a-1)位中,(j)的取值是固定的,与(M)保持一致,而(i)的取值是自由的
对于每一位来说,如果(j)取(0),那么(i)取(0,1)皆可
如果(j)取(1),那么(i)只能取(1)
设(M)的第(b)到第(a-1)位中,有(sum_0)个(0),那么就有(2^{sum_0})种取值
若(a=b),则令(sum_0=0)即可
从第(a)位到最高位(30位),由于(i,j)的取值分别和(N,M)保持一致,故只有(1)种取值
综上,(sum_{i=N}^{N+2^a-1}sum_{j=M}^{M+2^b-1}[i&j==0]lfloorlog_{2}(i+j)+1 floor=k3^b2^{sum_0})
其中(N&(2^{a}-1)==0,M&(2^{b}-1)==0,Nge 2^a,Mge 2^b,age b)
(k)是(N,M)位数的最大值,(sum_0)是(M)的第(b)位到第(a-1)位中(0)的个数
在笔者看来,数位(DP)最核心的思想就是分段求和
给定(X,Y),我们需要给(X),(Y)分别分段,然后两两组合计算并求和
比如题目给的(19\,26)这一样例
([0,19])即可分成([0,0],[1,1],[2,3],[4,7],[8,15],[16,19])
([0,26])即可分成([0,0],[1,1],[2,3],[4,7],[8,15],[16,23],[24,25],[26,26])
然后根据上面的方法两两组合求和,这里特别注意([0,0])和([0,0])不可以组合,这是题目的规定
这种分法大约需要把区间分为(60)段
假设(X)有(cnt)位,那么可以先分出([0,0],[1,1],[2,3],...[2^{cnt-2},2^{cnt-1}-1])这些区间
这些最多是(cnt)份
之后可以继续分出若干区间([2^{cnt-1},2^{cnt-1}+2^{a_1}-1],[2^{cnt-1}+2^{a_1},2^{cnt-1}+2^{a_1}+2^{a_2}-1]...)
由于(cnt-1>a_1>a_2>...ge 0),所以最多有(cnt-1)份
总共不超过(2cnt-1)份,考虑(Xle 1e9),故最多分成(61)份,每次计算需要不超过(4000)次求和
这样总共就需要进行约(4000*100000=4e8)次求和,这样就要求每次计算时复杂度是(O(1))的
我们需要预处理出每一个区间([N,N+2^a-1])的(N)的哪些位是(0),然后用前缀和求出区间中(0)的数量,这样可以快速得出(sum_0)的大小
此外还得预处理出(3)的若干次幂和(2)的若干次幂,不可以现算
我的代码用G++11提交T了一次,然后用G++17提交就A了,可见出题人卡得一手常数。当然,我相信在一定的优化之后,用所有的方法编译都是可以通过的。
代码如下
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<vector>
using namespace std;
//typedef unsigned long long ll;
typedef long long ll;
const ll mod=1000000007;
int posx[40],posy[40];
int bits(int num)//返回有几位
{
int cnt=0;
do
{
cnt++;
num>>=1;
}while(num);
return cnt;
}
ll power[4][35];
struct qujian
{
int st,free,len;//表示区间[st,st+2^free-1],其中st有len位
private:int pre_sum_act[35];
public:
int *pre_sum;//前缀和数组
void mp(int st_,int free_,int len_)
{
st=st_;free=free_;len=len_;
pre_sum=pre_sum_act+1;//防止求前缀和时访问下标-1
}
void pre()//求前缀和
{
pre_sum[-1]=0;
for(int i=0;i<31;i++)
{
pre_sum[i]=pre_sum[i-1]+!(st&(1<<i));
}
}
int num_0(int l,int r)//求[l,r]这个区间中,st中有几个0
{
return pre_sum[r]-pre_sum[l-1];
}
ll operator *(qujian &b)//两个区间中有几对i,j使得i&j==0
{
//注意一定使用指针或者引用访问,免得程序执行时进行拷贝,提高复杂度
ll ans=1;
qujian *x,*y;
x=this;
y=&b;
if(x->free<y->free)swap(x,y);
ans*=power[3][y->free];//题解中所说的3^b
ans*=power[2][y->num_0(y->free,x->free-1)];//题解中所说的2^sum_0
return ans;
}
}qjx[100],qjy[110];
int lim(int num,qujian qj[])
{
int weishu=bits(num);
int cnt=0;
qj[++cnt].mp(0,0,1);//[0,0]
for(int i=0;i<weishu-1;i++)
{
qj[++cnt].mp(1<<i,i,i+1);
}
/*注:这种写法分出来的区间可能会多一点,比如[1000,1111]本来可以看成一个区间,
这么写会拆成[1000,1011],[1100,1101],[1110,1110],[1111,1111]四个区间,总数不会超过62个
好处是比较好写(好写很重要),可以用简单的循环处理
*/
for(int st=1<<(weishu-1),i=weishu-2;i>=0;i--)
{
if((num&(1<<i))==0)continue;
qj[++cnt].mp(st,i,weishu);
st|=(1<<i);
}
if(num)qj[++cnt].mp(num,0,weishu);
return cnt;
}
int main()
{
//预处理幂次
power[2][0]=1;
power[3][0]=1;
for(int i=1;i<32;i++)power[2][i]=power[2][i-1]*2%mod;
for(int i=1;i<32;i++)power[3][i]=power[3][i-1]*3%mod;
int T;
scanf("%d",&T);
while(T--)
{
int X,Y;
scanf("%d%d",&X,&Y);
//区间划分
int qj_num_x=lim(X,qjx);
int qj_num_y=lim(Y,qjy);
for(int i=1;i<=qj_num_x;i++)
{
qjx[i].pre();//前缀和预处理
}
for(int j=1;j<=qj_num_y;j++)
{
qjy[j].pre();
}
ll ans=0;
//枚举不同的区间
for(int i=1;i<=qj_num_x;i++)
{
for(int j=1+(i==1);j<=qj_num_y;j++)
{
if(qjx[i].st&qjy[j].st)continue;
ll k=max(qjx[i].len,qjy[j].len);
ll ans1=qjx[i]*qjy[j];
ans+=k*ans1;
}
}
//最后不要忘了取模
ans=ans%mod;
printf("%lld
",ans);
}
}