3 1 3 5
1 4 7
/************************************************************************/
附上该题对应的中文题
ArrayVicky是个热爱数学的魔法师,拥有复制创造的能力。 一开始他拥有一个数列{1}。每过一天,他将他当天的数列复制一遍,放在数列尾,并在两个数列间用0隔开。Vicky想做些改变,于是他将当天新产生的所有数字(包括0)全加1。Vicky现在想考考你,经过100天后,这个数列的前M项和是多少?。
输入有多组数据。 第一行包含一个整数T,表示数据组数。T. (1≤T≤2∗103) 每组数据第一行包含一个整数M. (1≤M≤1016)
对于每组数据输出一行答案.
3 1 3 5
1 4 7
第一项永远为数字1,因此样例1输出1 第二天先复制一次,用0隔开,得到{1,0,1},再把产生的数字加1,得到{1,1,2},因此样例2输出前3项和1+1+2=4. 第三天先得到{1,1,2,0,1,1,2},然后得到{1,1,2,1,2,2,3},因此样例3输出前5项和1+1+2+1+2=7
出题人的解题思路:
其实Ai为i二进制中1的个数。每次变化Ak+2i=Ak+1 ,(k<2i)不产生进位,二进制1的个数加1。然后数位dp统计前m个数二进制1的个数,计算每一位对答案的贡献。只需考虑该位填1,其高位与低位的种数即可。
首先,我们先来看看100天后该数列前面的一些项:1,1,2,1,2,2,3,1,2,2,3,2,3,3,4,……
起初,我还在想怎么找规律啊什么的,后来突然发现了其中的猫腻
第0项: 0……0000 0个1 (该项无实际意义,但可简化求解过程)
第1项: 0……0001 1个1
第2项: 0……0010 1个1
第3项: 0……0011 2个1
第4项: 0……0100 1个1
第5项: 0……0101 2个1
第6项: 0……0110 2个1
第7项: 0……0111 3个1
...
...
...
而我们要求的前m项和,其实就是前m项1~m的二进制数表示的1的总个数
另外,我们可以发现,二进制数从低位到高位,对于第i位,01交替都是以为周期,那我们只要看有多少个这样的周期,以及最后一个不满一个周期里有多少个1就是我们要求解的
即对于第i位,我们只需求出周期数和最后一个不满一个周期里有多少个数
因为为周期的位,有一半是1,一半是0,所以一个周期会贡献个1
而最后一个周期,如果它的个数超过周期数的一半,它才会有所贡献,因为一个周期里前一半都是0,而后一半才全是1
故最终我们可以得到如下代码:
#pragma comment(linker, "/STACK:1024000000,1024000000") #include<stdio.h> #include<string.h> #include<stdlib.h> #include<queue> #include<stack> #include<math.h> #include<vector> #include<map> #include<set> #include<cmath> #include<string> #include<algorithm> #include<iostream> #define exp 1e-10 using namespace std; const int N = 100005; const int M = 10005; const int inf = 1000000000; const int mod = 10007; int main() { int i,t; __int64 m,a,b,sum,k,j; scanf("%d",&t); while(t--) { scanf("%I64d",&m); for(sum=0,j=1,i=1;i<55;i++) { k=j<<i;//printf("##%I64d ",k); a=m/k; b=m%k; //printf("%I64d %I64d\n",a,b); if(b+1>k/2) sum=sum+a*k/2+b-k/2+1; else sum=sum+a*k/2; } printf("%I64d\n",sum); } return 0; }菜鸟成长记