2069: Saruman’s Level Up(思维枚举+质数分解求组合数)

题目链接:http://acm.csu.edu.cn/csuoj/problemset/problem?pid=2069

题目:
Description
Saruman’s army of orcs and other dark minions continuously mine and harvest lumber out of the land surrounding his mighty tower for N continuous days. On day number i, Saruman either chooses to spend resources on mining coal and harvesting more lumber, or on raising the level (i.e., height) of his tower. He levels up his tower by one unit only on days where the binary representation of i contains a total number of 1’s that is an exact multiple of 3. Assume that the initial level of his tower on day 0 is zero. For example, Saruman will level up his tower on day 7 (binary 111), next on day 11 (binary 1011) and then day 13, day 14, day 19, and so on. Saruman would like to forecast the level of his tower after N days. Can you write a program to help?

Input
The input file will contain multiple input test cases, each on a single line. Each test case consists of a positive integer N < 1016, as described above. The input ends on end of file.

Output
For each test case, output one line: “Day N: Level = L”, where N is the input N, and L is the number of levels after N days.

Sample Input
2
19
64
Sample Output
Day 2: Level = 0
Day 19: Level = 5
Day 64: Level = 21

题意:就是你在建一个塔,开始它是0层,当某一天的天数,转化为2进制,每一位为1的个数是3的倍数,就可以把塔升级一层,比如第7天,它的二进制为111,3个1,塔就可以升级一层,比如第64天,它的二进制为111111,6个1,塔也可以升级一层。 输入一个天数,问你当天,塔的层数是多少。

思路: 输入最大的天数是10的十六次方,比2的54次方小。就最多54位,就可以每位枚举下,但不是单纯的枚举,可以优化处理下。 比如说10101010这个二进制,它的下限就是10000000,10000000的升级塔的次数就是C(7,3)+C(7,6), 这个算的其实就是10101010的第一个1为0的时候的情况,然后考虑第一个1为1的情况 就把10101010减去10000000,cnt=1,cnt表示前面有几个1,
等于101010,然后继续考虑它的下限,考虑第二个1是否取不取,直到减到0。但是还有个特例要特殊判断一下,见代码。

代码:

#include <iostream>
#include <math.h>
#include <stdio.h>
#include <string.h>
#include <string>
#include <vector>

using namespace std;
const long long inf=1e16;
long long N,sum,id,cnt,ans,N1;
long long two[56];//2的多少次方
long long vis[56],e[56];
long long a[56];//2的多少次方有多少个可以支配的1
vector <long long > primes;//质数表

void is_prime()//打出55以内的质数表
{
    memset(vis,0,sizeof(vis));
   long long m=sqrt(56+0.5);
    for(long long i=2; i<=m; i++)
   for(long long j=i*i; j<56; j+=i)
    vis[j]=1;
    for(int j=2; j<55; j++)
        if(!vis[j])
        primes.push_back(j);
}

void add_integer(long long n,long long d )//质因数分解
{
    for(long long i=0; i<primes.size(); i++)
    {
        while(n%primes[i]==0)
        {
            n/=primes[i];
            e[i]+=d;
        }
        if(n==1)
            break;
    }
}

void init()
{
    for(long long i=0; i<56; i++)
    {
        two[i]=pow(2,i);

    }

    a[0]=1;
    for(long long  i=1; i<56; i++)
    {
        a[i]=i;
    }
}

long long C(long long n, long long m)//用质因数分解求组合数
{
    memset(e,0,sizeof(e));
    if(m<n-m)
        m=n-m;
    double ans1=1;
    long long ans;
    for(long long i=m+1; i<=n; i++)
        add_integer(i,1);
    for(long long i=1; i<=n-m; i++)
        add_integer(i,-1);
    for(long long i=0; i<primes.size(); i++)
        {
            if(e[i]!=0)
                {
                  ans1*=(double)pow(primes[i]*1.0,e[i]*1.0);//pow返回值为double,用整形会卡精度
                }
        }
        ans=ans1;
    return ans;
}

long long solve(long long x,long long cnt)
{
   long long sum1=0;
   for(long long i=3; i<56; i+=3)
   {
       if(x>=i-cnt&&i-cnt>=0)
        {
            sum1+=C(x,i-cnt);
        }

   }
   return sum1;
}

int main()
{
    init();
    is_prime();
    while(scanf("%lld",&N)!=EOF)
    {
        ans=0;
        cnt=0;//前面有几个1
        N1=N;
        while(N1)
        {
              for(long long i=0; i<56; i++)
              {
                  if(N1>two[i]&&N1<two[i+1])
                  {

                      id=i;
                      break;
                  }
                  if(N1==two[i])
                  {
                      id=i;
                      break;
                  }
              }
              ans+=solve(a[id],cnt);
              cnt++;
              N1=N1-two[id];
              if(N1==0&&cnt%3==0&&N%2!=1)//当我前面的1的个数是3的倍数且二进制最后一位为0的话,会少算一次.
                ans++;
        }
        printf("Day %lld: Level = %lld\n",N,ans);
    }
    return 0;
}

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值