数位dp·

定义:

数位dp的尝试并不特殊,绝大多数都是线性展开,类似于从左往右的尝试

数位dp时在数字的每一位上进行线性展开

解题的核心在于:可能性的整理,排列组合

题目一

找所有的可能:

1位数:9

2位数:9 * 9

3位数:9 * 9 * 8 * 7

因此找到规律

代码:

# include <stdio.h>

int main()
{
	int n;
	int ans;
	scanf("%d", &n);
	if (n == 0)
		printf("1");
	else
	{
		for (int s=9, i=9, k=2; k<=n; --i, ++k)
		{
			s = s * i;
			ans = ans + s;
		}
		printf("%d", &ans);
	}
} 

题目二

代码:

# include <stdio.h>
# include <string.h>
int len; //digit数组的长度 

//offset是辅助变量,完全为了方便提取num中某一位数字,不是关键变量
//剩下lenn位没有决定
//如果之前的位已经确定比对应的cur小,那么free == 1,表示接下的数字可以自由选择
//如果之前的位和对应的cur一样,那么free == 0,表示接下的数字不能大于cur
//如果之前的位没有使用过数字,fix == 0
//如果之前的位已经使用过数字,fix == 1 
int cmp(int* digit, int num, int offset, int lenn, int free, int fix)
{
	if (lenn == 0)
	{
		if (fix == 0)
			return 0;
		else
			return 1;
	}
	int ans = 0;
	if (fix == 0)
	{
		ans = ans + cmp(digit, num, offset/10, lenn-1, 1, 0);
	}
	int cur = (num / offset) % 10;
	//cur 表示当前位的数字 
	if (free == 0)
	{
		for (int i=0; i<len; ++i)
		{
			if (cur > digit[i])
				ans = ans + cmp(digit, num, offset/10, lenn-1, 1, 1);
			else if (cur == digit[i])
				ans = ans + cmp(digit, num, offset/10, lenn-1, 0, 1);
			else
				break;
		}
	}
	else
	{
		ans = ans + len * cmp(digit, num, offset/10, lenn-1, 1, 1);
	}
}

int main()
{
	scanf("%d", &len);
	char a[len];
	for (int i=0; i<len; ++i)
	{
		scanf("%c", &a[i]);
	}
	int digit[n];
	for (int i=0; i<len; ++i)
	{
		digit[i] = (int)a[i];
	}
	int num;
	scanf("%d", &num);
	int tmp = num;
	int offset = 1;
	int lenn = 0;
	while (tmp>0)
	{
		tmp = tmp / 10;
		lenn = lenn + 1;
		offset = offset * 10;
	}
	int x = cmp(digit, num, offset, lenn, 0, 0);
}

题目三

 因为是字符串类型的num1和num2,说明用long long类型是存储不了这么大的数字的

所以我们要通过对字符串的处理来解决此问题

所以我们就要可以算0~(num1-1)中符合条件的

然后再计算0~(num2)中符合条件的

最后再将两者相减,就可以计算出答案

代码:

# include <stdio.h>
int max;
int min;
int len;
char num[1000];
int dp[100][100][2]; // dp[len][max+1][2]

//注意
//数字:char num[]
//累加和最小要求: int min
//累加和最大要求: int max
//以上都为全局静态变量 
//递归含义: 
//从num的高位出发(也就是数组中的第0位,当前来到i位上) 
//之前决定的数字累加和是sum 
//之前的决定已经比num小,后续可以自由选择数字,那么free = 1 
// 之前的决定和num一样,后续不可以自由选择数字,那么free = 0 
//返回有多少种可能 
int cmp(int i, int sum, int free)
{
	if (sum > max)
		return 0;
	if (sum + (len-i)*9 < min)
		return 0;
	if (i == len)
		return 1;
	if (dp[i][sum][free] != -1)
		return dp[i][sum][free];
	//cur:num当前位的数字
	int cur = num[i]-'0';
	int ans = 0;
	if (free == 0)
	{
		//还不能自由选择 
		for (int i=0; i<cur; ++i)
		{
			ans = ans + cmp(i+1, sum+i, 0);
		}
		ans = ans + cmp(i+1, sun+cur, 1);
	}
	else
	{
		for (int i=0; i<10; ++i)
		{
			ans = ans + cmp(i+1, sum+i, 0);
		}		
	}
	dp[i][sum][free] = ans;
	return ans;
}

int main()
{
	scanf("%d", &min);
	scanf("%d", &max);
	scanf("%d", &len);
	for (int i=0; i<len; ++i)
		scanf("%c", &num[i]);
	memset(dp, -1; sizeof(dp));
	int ans = cmp(0, 0, 0);
	
	for (int i=0; i<len; ++i)
		scanf("%c", &num[i]);	
	memset(dp, -1; sizeof(dp));
	ans = ans - cmp(0, 0, 0);
}

题目四

如果n的位数是len位,先计算位数少于len的数中,每一位都互不相同的正整数个数,并累加

所有1位数中,每一位都互不相同的正整数个数 = 9

所有2位数中,每一位都互不相同的正整数个数 = 9 * 9

所有3位数中,每一位都互不相同的正整数个数 = 9 * 9 * 8

----- 比 len 少的位数都累加--------

然后计算第 len 位

分为:小于 n 的最高位的数字,和等于 n 最高位的数、

代码:

# include <stdio.h>

int n;
int cnt[100];
int statue;
//cnt[i]:
//一共长度为len,还剩i位没有确定,确定的前缀为 len-i 位,且确定的前缀不为空 
// 0~9一共10个数字,没有选择的数字剩下10-(len-i)个 
//那么在后续的i位上,有多少种排列 
//比如len = 4 
//cnt[4]不计算 
//cnt[3] = 9 * 8 * 7
//cnt[2] = 8 * 7 
//cnt[1] = 7 
//cnt[0] = 1 表示前缀已经确定,后续也没有了,那么就是一种排列 
void made(void)
{
	cnt[0] = 1;
	for (int i=1, k = 10-len+1; i<len; ++i, ++k)
		cnt[i] = cnt[i-1]*k;
}

int cmp(int* cnt, int len, int offset, int statue)
{
	if (len == 0)
		return 1;
	int ans = 0;
	int cur = (n/offset) % 10;
	for (int i=0; i<cur; ++i)
	{
		if ((statue & (1<<i)) == 0)
			ans = ans + cnt[len-1];
	}
	if ((statue & (1<<i)) == 0)
		ans = ans + cmp(cnt, len-1, offset/10, statue | (1<<i));
	return ans;
}

int main()
{
	made();
	int ans = 0;
	int len = 0;
	scanf("%d", &len);
	statue = 1 << (len+1);
	//先计算位数少于len的数 
	if (len >= 2)
	{
		ans = 9;
		for (int i=2, a=9, b=9; i<len; i++, b--)
		{
			a = a * b;
			ans = ans + a;
		}
	}
	//下面计算一定有len位的数字
	scanf("%d", &n);
	int offset = 1;
	int tmp = n;
	while (tmp>0)
	{
		tmp = tmp / 10;
		offset = offset *10;
	}
	int cur =  n / offset;
	//小于n最高位数字的情况 
	ans = ans + (cur - 1)*cnt[len-1];
	//后续累加上,等于n最高位数字的情况
	ans = ans + cmp(cnt, len-1, offset/10, 1<<cur);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值