Array
Accepts: 112
Submissions: 324
Time Limit: 2000/1000 MS (Java/Others)
Memory Limit: 131072/131072 K (Java/Others)
问题描述
Vicky是个热爱数学的魔法师,拥有复制创造的能力。 一开始他拥有一个数列{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
Hint
第一项永远为数字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
思路:虽然题目有写100天后其实那是吓唬人的,因为输入范围只有1e16根本到不到100天的。
此题有多种解法。
方法一:根据Hint找找规律很容易发现:
(1)每一天的长度是上一天长度的二倍加一。
(2)每一天的总和是上一天总和的二倍加上上一天的长度再加上中间那个一。
可以预处理出每一天的长度和总和,然后可以二分找出要求的那项前面最近的一天。
答案就是那天的总和加上中间的一个一再加上后面的一小块。
后面那块可以根据数列的开头递归算出。
#include <bits/stdc++.h>
using namespace std;
#define ll long long
ll len[65],sum[65];
void init()
{
len[0]=sum[0]=1LL;
for(int i=1;i<=60;i++)
{
len[i]=len[i-1]*2+1;
sum[i]=sum[i-1]*2+1+len[i-1];
}
}
ll solve(ll k)
{
if(k<=1)return k;
int l=lower_bound(len,len+61,k)-len-1;
return sum[l]+1+solve(k-len[l]-1)+(k-len[l]-1);
}
int main()
{
init();
int t;
scanf("%d",&t);
while(t--)
{
ll n;
scanf("%I64d",&n);
printf("%I64d\n",solve(n));
}
return 0;
}
方法二:其实如果再看仔细点就会发现其实数列的每一项都是那个数二进制1的个数。
第n项 | n的二进制 | 第n项的值 |
0 | 0000 | 0 |
1 | 0001 | 1 |
2 | 0010 | 1 |
3 | 0011 | 2 |
4 | 0100 | 1 |
5 | 0101 | 2 |
6 | 0110 | 2 |
7 | 0111 | 3 |
8 | 1000 | 1 |
9 | 1001 | 2 |
10 | 1010 | 2 |
11 | 1011 | 3 |
12 | 1100 | 2 |
13 | 1101 | 3 |
14 | 1110 | 3 |
15 | 1111 | 4 |
此题就变成输入m输出从0到m所有数二进制中一的个数和。
然后根据从0开始每一个二进制的位置的01出现是有规律的。
找出每个位置的周期直接计算每个位置二进制一的个数,相加就是答案。
#include <bits/stdc++.h>
using namespace std;
#define ll long long
ll solve(ll k)
{
ll res=0;
for(int i=0;i<=60;i++)
{
if((1LL<<i)>k)break;
ll t=1LL<<(i+1);
res+=(k+1)/t*t/2;
if((k+1)%t>t/2)res+=(k+1)%t-t/2;
}
return res;
}
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
ll n;
scanf("%I64d",&n);
printf("%I64d\n",solve(n));
}
return 0;
}
方法三:同样根据方法二的规律。
很明显可以把输入的m变成二进制后数位dp直接求。
#include <bits/stdc++.h>
using namespace std;
#define ll long long
int bit[65];
ll dp[65][65];
ll dfs(int len,int num,int ma)
{
if(len==0)return (ll)num;
if(!ma&&dp[len][num]>=0)return dp[len][num];
int maxx=1;
if(ma)maxx=bit[len];
ll sum=0;
for(int i=0;i<=maxx;i++)
{
sum+=dfs(len-1,num+(i==1),ma&&i==bit[len]);
}
if(!ma)dp[len][num]=sum;
return sum;
}
ll solve(ll k)
{
int len=0;
while(k)
{
bit[++len]=k%2;
k/=2;
}
return dfs(len,0,1);
}
int main()
{
memset(dp,-1,sizeof dp);
int t;
scanf("%d",&t);
while(t--)
{
ll n;
scanf("%I64d",&n);
printf("%I64d\n",solve(n));
}
return 0;
}