CSU 2069 Saruman‘s Level Up(数位dp/组合计数)

2069: Saruman’s Level Up

Submit Page    Summary    Time Limit: 5 Sec     Memory Limit: 512 Mb     Submitted:176     Solved:32    

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

Hint

Source

pacnw2012


        大致题意:让你求在1到n范围内转换成二进制后1的个数是3的倍数的数字的个数。
       典型的数位dp。设置dp[len][x]表示长度为len的二进制数字中含有x个1的个数。显然又转移方程dp[len][x]=dp[len-1][x-1]+dp[len-1][x]。记忆化搜索考虑lim的影响按照套路做即可。但是会发现对于1e8以上的数据就已经会超时了。经过分析可以发现,当len很大而x很小的时候,dfs的层数会比较多,时间消耗比较大。我们可以考虑增大这个x。容易发现,我计算长度为len的数字中1为x的个数,与计算长度为len的数字中0为len-x的个数是等价的。所以说,我们可以反过来求,设置dp[len][x][0]表示0出现x次的个数,dp[len][x][1]表示1出现x次的个数。在求解的时候根据x和len-x的大小关系选择一个更优的来求解。具体见代码:
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<string>
#include<iostream>
#include<map>
#define N 100010
#define LL long long
using namespace std;

LL dp[64][64][2],num[64];

LL dfs(int len,int x,bool lim,bool t)
{
	if (x>len) return 0;
	if (len==0) return x==0;
	if (!lim&&dp[len][x][t]) return dp[len][x][t];
	int up=lim?num[len]:1;
	LL res=0;
	for(int i=0;i<=up;i++)
		res+=dfs(len-1,x-(i==t),lim&&up==i,t);
	if (!lim) dp[len][x][t]=res;
	return res;
}

LL cal(LL x)
{
	int tot=0;
	while(x)
	{
		num[++tot]=x&1;
		x/=2;
	}
	LL res=0;
	for(int i=3;i<=tot;i+=3)
	{
		int r=tot-i;
		if (i>r) res+=dfs(tot,i,1,1);
			else res+=dfs(tot,r,1,0);
	}

	return res;
}


int main()
{
	LL n;
	memset(dp,0,sizeof(dp));
	while(~scanf("%lld",&n))
	{
		printf("Day %lld: Level = %lld\n",n,cal(n));
	}
}
        此外,还可以考虑用组合数学的方法计算。对于一个长度为len的二进制数字从最右边开始标号,考虑枚举一个二进制数位i,可以提前预处理出i+1~len的1的个数s,然后枚举前面的i位中1的个数,用组合的方法计算 Σc(i,j),其中j从0到i-1(因为最高位为0才能使前面任意取),且保证(s+j)%3==0,当然要保证s+j>0。具体见代码:
#include<cstdio>
#define LL long long
#define N 100010
using namespace std;

LL c[61][61],s[61],n,tot,ans;

void init()
{
    c[0][0]=1;
    for(int i=1;i<=60;i++)
        for(int j=0;j<=i;j++)
            c[i][j]=c[i-1][j]+c[i-1][j-1];
}

int main()
{
    init();
    while(~scanf("%lld",&n))
    {
        ans=tot=0;
        printf("Day %lld: Level = ",n);
        while(n)
        {
            s[++tot]=n&1; n>>=1;
            s[tot]+=s[tot-1];
        }
        for(int i=1;i<=tot;i++)
        {
            int last=s[tot]-s[i];
            if (s[i]-s[i-1]==0) continue;
            for(int j=0;j<i;j++)
            {
                if (j+last==0) continue;
                if ((j+last)%3) continue;
                ans+=c[i-1][j];
            }
        }
        if (s[tot]%3==0) ans++;
        printf("%lld\n",ans);
    }
    return 0;
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值