ACM解题总结——HihoCoder1031

本文介绍一种使用动态规划解决特殊数字序列问题的方法。针对包含4或7的“好”数字序列,采用数位DP策略高效计算第k个元素。文章详细阐述了状态定义、转移方程及具体实现步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

(p.s: 第2道数位dp的题目,不需要考虑前导0的问题,比hihocoder1033相对简单一些。。。。)

题目来源:
    HihoCoder1301

题目要求:
    对于一个十进制的数字,我们规定如果数字串中包含数字4或7,那么说明它是一个“好”的数字,现在将所有的“好”的数字从小到大排列,给出一个数字k,要求给出排在第k位的“好”数字。

解答:
    本题的数据范围很大,通过枚举的方式找到答案的策略是不现实的,也一定会超时。比较高效的解法是对于各个数位上的数字进行动态规划求解。

·状态定义:
    对于数字位数在i位以内的数数字,定义状态如下:
        dp[i][0]:  最高位不是4和7的情况下,i位数以内的“好”数字的数目。
        dp[i][1]: 最高位是4和7的情况下,i位数以内的“好”数字的数目。
    例如:如果i = 1, 1位数中的“好”数字只有4和7,因此:dp[1][1] = 1, dp[1][0] = 0; 如果 i = 2, 那么,如果十位数是4或者7,个位数的取任何数字,该数字都是“好”的,因此dp[2][1] = 10, 而十位数字如果是其他的数字,那么个位数字就一定得是4或者7,才可以使得数字是“好”的,因此dp[2][0] = 2。
    对于更平凡的情况,dp[i][0]和dp[i][1]的计算方式如下:
    dp[i][0]表示最高位不是4和7的情况下的i位数以内的“好”数字个数,因此,除去第i位以后,就必须要争它的低i-1位数字是“好”的,而i-1位以内的“好”数字的个数又分为两种情况:如果i-1位中的最高位不是4或7,那么,“好”数字的个数为dp[i-1][0],这样情况有:0, 1, 2, 3, 5, 6, 8, 9,共8中情况;如果i-1位中的最高位是4或7,那么,“好”数字的个数为dp[i-1][1],这样的情况有4,7两种。因此:
            dp[i][0] = dp[i-1][0] * 8 + dp[i-1][1] * 2;
    dp[i][1]表示最高位是4或7的情况下的i位数中的“好”数字的个数,此时由于最高位已经包含4或7,因此它的低i-1位无论去什么值,整个数字都是合法的,而i-1位数字共有10^(i-1)个,因此:
            dp[i][1] = 10 ^ (i-1)

    通过计算表明,对于给定的数据范围,20位的十进制数字,就可以包含所有的输入情况。因此i的最大值为20。通过反复的迭代求解,就可以将整个状态表完成。

·计算:
    给出状态表后,对于给定的数字k,需要找到排在第k位的“好”数字,步骤如下:
    (1) 定义sum[i]表示i位数以内的所有“好”数字的数目,显然:
            s[i] = dp[i][0] * 8 + dp[i][1] * 2
    (2) 遍历s[1], s[2], ... s[20], 如果对于某个数值L, s[L-1] < k, 而s[L] ≥ k,那么说明我们要找的数字是一个L位的数。然后将k减小s[L-1],接下来的工作是寻找L位数中的排位在第k位的数字。
    (3) 对于某一个确定的L值,定义t[j],表示最高位取1, 2, ... j的情况下的“好”数字的数目,t[j]的计算方式如下:
        (i) 显然:t[0] = dp[L][0]
        (ii) 对于平凡的t[j],如果j不是4也不是7,那么:t[j] = t[j-1] + dp[L][0]; 如果j是4或7,那么:t[j] = t[j-1] + dp[L][1]
    通过以上的方法就可以计算出t[0], t[1], t[2] ... t[9]的值。
    (4) 遍历t[1], t[2], t[3]...t[9],对于某个数值M,如果t[M-1] < k, 而t[M] ≥ k,那么说明当前最高位(第L位)的数值就是M,同时将k减少t[M-1],并将L减1, 接下来的工作是寻找L位数中的排在第k位的数字。
    (5) 本步骤将会反复地迭代进行,本步骤分为三种情况:
        (i) 如果上一轮迭代中的M值为4或者7,那么当前位置取任何数字都是可以的,因此排在第k位的数字就是k-1(由于存在数字0),因此,直接将剩余的L-1填充数值k-1即可,此时迭代结束,输出最后的答案,注意前导0的处理。
        (ii) 如果上一轮的迭代中的M值不是4,也不是7,那么本步骤同样需要为数值L计算序列t[j],然后遍历t[0], t[1], ... t[9] (注意,这个时候的L位数字只是最终结果的一部分,因此最高位是可以取0的,我们需要从t[0]开始遍历),同样,当遇到某个数值M,使得:t[M-1] < k, 而t[M] ≥ k,则说明当前位的数值就是M。然后L减1,继续执行本步骤。
        (iii) 如果当前的迭代中L的值已经为0,那么说明最终的答案已经产生,迭代结束。

以上是计算的步骤。

输入输出格式:
    输入:
输入数据包含一行一个整数k
    输出:
输出数据包含一行,表示对应的十进制数字串。

数据范围:

     0 ≤ k ≤ 1,000,000,000,000,000,000 (10^18)


程序代码:

/****************************************************/
/* File        : HihoCoder1301                      */
/* Author      : Zhang Yufei                        */
/* Date        : 2016-05-09                         */
/* Description : HihoCoder ACM program. (submit:g++)*/
/****************************************************/

#include<stdio.h>
#include<math.h>

/*
 * The main program.
 */
int main(void) {
    
	unsigned long long dp[21][2];
    unsigned long long weight[20];
    
    weight[0] = 1;
    for(int i = 1; i < 20; i++) {
    	weight[i] = 10 * weight[i - 1];
    }
    
	dp[1][0] = 0;
	dp[1][1] = 1;
	dp[2][0] = dp[1][0] * 7 + 2 * weight[0];
	
	for(int i = 2; i <= 20; i++) {
		dp[i][1] = weight[i - 1];
		dp[i + 1][0] = dp[i][1] * 2 + dp[i][0] * 8; 
	}
 
	unsigned long long k;

	scanf("%llu", &k);
	
	int i;
	int tag = 0;
	
	for(i = 1; i <= 20; i++) {
	   if(dp[i][0] >= k) {
			break;
	   }
	}
	
	while(k > 0) {
		i--;
		if(i == 0) {
			break;
		}
		if(tag == 0) {
			unsigned long long s = 0;
			int j;
			for(j = 0; j < 10; j++) {
				int index;
				if(j != 4 && j != 7) {
					index = 0;
				} else {
					index = 1;
				}
				if(s + dp[i][index] >= k) {
					printf("%d", j);
					k -= s;
					if(j == 4 || j == 7) {
						tag = 1;
					}
					break;			
				} else {
					s += dp[i][index];
				}
			}
			
		} else {
			k--;
			while(i > 0) {
				printf("%d", k / weight[i - 1]);
				k %= (int) weight[i - 1];
				i--;
			}
			break;	
		}
	}

   	printf("\n");
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值