定义:
数位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);
}