• 2020 ICPC Shanghai C


    题目链接:
    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);
    	}
    }
    
    
  • 相关阅读:
    多测师讲解python _函数的传递_高级讲师肖sir
    多测师讲解pthon _函数__return_高级讲师肖sir
    多测师讲解python _函数中参数__高级讲师肖sir
    前端 HTML body标签相关内容 常用标签 图片标签 <img/>
    mysql 操作sql语句 操作数据库
    python web框架 MVC MTV
    linux dmesg 查看系统故障信息
    linux uniq 命令
    linux md5sum命令
    Python 字典 items() 方法
  • 原文地址:https://www.cnblogs.com/ssdfzhyf/p/14529316.html
Copyright © 2020-2023  润新知