zoukankan      html  css  js  c++  java
  • 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);
    	}
    }
    
    
  • 相关阅读:
    单例模式
    Curator Zookeeper分布式锁
    LruCache算法原理及实现
    lombok 简化java代码注解
    Oracle客户端工具出现“Cannot access NLS data files or invalid environment specified”错误的解决办法
    解决mysql Table ‘xxx’ is marked as crashed and should be repaired的问题。
    Redis 3.0 Cluster集群配置
    分布式锁的三种实现方式
    maven发布项目到私服-snapshot快照库和release发布库的区别和作用及maven常用命令
    How to Use Convolutional Neural Networks for Time Series Classification
  • 原文地址:https://www.cnblogs.com/ssdfzhyf/p/14529316.html
Copyright © 2011-2022 走看看