Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 131072/131072 K (Java/Others)
Total Submission(s): 417 Accepted Submission(s): 211
题目大意:
一开始vicky拥有一个数列{1}。每过一天,他将他当天的数列复制一遍,放在数列尾,并在两个数列间用0隔开。Vicky想做些改变,于是他将当天新产生的所有数字(包括0)全加1。Vicky现在想考考你,经过100天后,这个数列的前M项和是多少?。
解题思路:首先我们可以先写出某天的序列{1,1,2,1,2,2,3,1,2,2,3,2,3,3,4}。我们可以首先将第i天的序列和以及序列长度递推出来,如果给的M正好是一个某天后的序列长度
,那么我们很容易可以得到序列和。但是如果M不是某天后的序列长度,我们需要将最长的序列和先拿出来求这段子序列的和。然后再处理剩下的序列,同时应该把这时候剩下的序列的第一个元素即1减掉,减掉后剩下的序列是他前边求出的序列元素+1后得到的,这时候,我们应该继续求出剩下序列的最长序列,然后拿出来求和,但是这时候求的序列元素应该都+1,然后再减去第二次拿掉序列后的第一个元素即2减掉,重复上述操作。 (不好描述,看代码)
#include<stdio.h>
using namespace std;
typedef long long LL;
LL num[100],sum[100];
void prin(){ //预处理出来第i天的序列长度,第i天的序列和
num[1] = 1;
for(int i = 2; i <= 63; i++){
num[i] = num[i-1]*2 + 1;
}
sum[1] = 1;
for(int i = 2; i <= 63; i++){
sum[i] = sum[i-1]*2+num[i-1]+1;
}
}
int main(){
int T , p;
LL n;
prin();
scanf("%d",&T);
while(T--){
scanf("%lld",&n);
LL ans = 0,coun = 0;
while(n){
p = 1;
while(num[p] <= n){ //找到最长的序列
p++;
}
p--;
ans += sum[p] + num[p]*coun; //截取出来的序列的序列和
n -= num[p]; //求出剩下序列长度
if(n != 0){
coun++; //记录经过多少次复制得到
ans += coun; //减掉序列开头那个元素
n--; //序列长度减一
}
}
printf("%lld\n",ans);
}
return 0;
}
原文:http://www.cnblogs.com/chengsheng/p/5010728.html