1081.度的数量 题目链接
数 X X X 恰好等于 K K K 个互不相等的 B B B 的整数次幂之和即数 X X X 的 B B B 进制表示下有 K K K 位是 1 1 1,其余位均为 0 0 0。
数位DP的技巧:
设数 n n n 在 B B B 进制表示下有 m m m 位,第 i ( 0 ≤ i < m ) i\ (0\le i
当前处理到第 i i i 位时,且前所有位上共有 l a s t last last 个 1 1 1 时:
上述全部都是叶子节点为左子结点的情况,最后还要特判树的右支是否满足性质。
对于组合数 C n m C_n^m Cnm,我们可以根据递推公式 C n m = C n − 1 m + C n − 1 m − 1 C_n^m=C_{n-1}^m+C_{n-1}^{m-1} Cnm=Cn−1m+Cn−1m−1 ,通过两重循环预处理。
代码实现:
#include
#include
#include
using namespace std;
const int N = 35;
int K, B;
int f[N][N];
void init(){ //预处理组合数
for (int i = 0; i < N; i ++)
for (int j = 0; j <= i; j ++){
if (!j) f[i][j] = 1;
else f[i][j] = f[i - 1][j] + f[i - 1][j - 1];
}
}
int dp(int n){
if (!n) return 0; //0不满足性质,返回0
vector <int> nums;
while (n) nums.push_back(n % B), n /= B; //提取n在B进制表示下的每一位
int res = 0, last = 0; //res存储答案数,last存储在前所有位中1的个数
for (int i = nums.size() - 1; i >= 0; i --){
int x = nums[i];
if (x){ //如果当前位不是0,则这一位一定可以填0,答案加上剩下i位中填K-last个1,剩下全为0的方案数
res += f[i][K - last];
if (x > 1){ //如果当前位大于1,则这一位一定可以填1,答案加上剩下i位中填K-last-1个1,剩下全为0的方案数
if (K - last - 1 >= 0) res += f[i][K - last - 1];
break; //由于满足性质的数在B进制表示下不会出现除0,1以外的数,所以剩下数均不满足性质,直接返回
}
else{
last ++; //当前位是1,还要继续往下讨论,1的个数+1
if (last > K) break; //如果1的个数超过上限,返回
}
}
if (!i && last == K) res ++; //判断n是否满足性质
}
return res;
}
int main(){
init();
int l, r;
scanf("%d %d %d %d", &l, &r, &K, &B);
printf("%d", dp(r) - dp(l - 1));
return 0;
}
1082.数字游戏 题目链接
先来考虑左支能够直接算出方案数的部分。
设 f [ i , j ] f[i,j] f[i,j] 表示共有 i i i 位,且最高位是 j j j 的“从左到右位数字呈非下降关系”的数的个数,有:
f [ i , j ] = ∑ k = j 9 f [ i − 1 , k ] f[i,j]=\sum\limits_{k=j}^9 f[i-1,k] f[i,j]=k=j∑9f[i−1,k]
与上题的分析方法类似,当前处理到第 i i i 位,且上一位数字是 l a s t last last 时:
代码实现:
#include
#include
#include
using namespace std;
const int N = 15;
int f[N][N];
void init(){
for (int i = 0; i <= 9; i ++) f[1][i] = 1;
for (int i = 2; i < N; i ++)
for (int j = 0; j <= 9; j ++)
for (int k = j; k <= 9; k ++)
f[i][j] += f[i - 1][k];
}
int dp(int n){
if (!n) return 1; //0也是满足性质的数
vector <int> nums;
while (n) nums.push_back(n % 10), n /= 10;
int res = 0, last = 0;
for (int i = nums.size() - 1; i >= 0; i --){
int x = nums[i];
for (int j = last; j < x; j ++)
res += f[i + 1][j];
if (x < last) break;
last = x;
if (!i) res ++;
}
return res;
}
int main(){
init();
int l, r;
while (~scanf("%d %d", &l, &r))
printf("%d\n", dp(r) - dp(l - 1));
return 0;
}
1083.Windy数 题目链接
在前两道题中,一个数的前导零对该数是否满足性质没有影响。但在本题中,如果存在前导零,如果没有特判的话,会导致下一位只能从 2 2 2 开始填。因此本题中对有前导零的数单独求解,用树的结构求首位非零的所有满足方案的数的个数。
先来考虑所有左支的情况。设 f [ i , j ] f[i,j] f[i,j] 表示共有 i i i 位,且最高位是 j j j 的所有是Windy数的数的个数,有:
f [ i , j ] = ∑ k = 0 , 1 , . . . , 9 , ∣ k − j ∣ ≥ 2 f [ i − 1 , k ] f[i,j]=\sum\limits_{k=0,1,...,9,|k-j|\ge 2}f[i-1,k] f[i,j]=k=0,1,...,9,∣k−j∣≥2∑f[i−1,k]
注意初始化时要有 f [ 1 , 0 ] = 1 f[1,0]=1 f[1,0]=1,因为最后一位是 0 0 0 的方案数会从这个状态转移。
对于所有含有前导零的数,枚举数的位数和最高位,可以通过 f f f 数组直接得到方案数。
对于首位非零的数,按照树的结构,从高位到低位求解。当前处理到第 i i i 位,且上一位数字是 l a s t last last 时:
这一位可以填在 0 ~ a i − 1 0~a_i-1 0~ai−1之间 且与 l a s t last last 作差的绝对值不小于 2 2 2 的数,如果 ∣ a i − l a s t ∣ < 2 |a_i-last|<2 ∣ai−last∣<2,那么之后的所有数都不会是Windy数了,直接返回,否则继续枚举下一位,记得更新 l a s t = a i last=a_i last=ai。 l a s t last last 的初始值必须 ≤ − 1 \le -1 ≤−1 或 ≥ 11 \ge 11 ≥11,为了确保第一位能取到 1 ~ 9 1~9 1~9。
代码实现:
#include
#include
#include
using namespace std;
const int N = 11;
int f[N][10];
void init(){
for (int i = 0; i <= 9; i ++) f[1][i] = 1;
for (int i = 2; i < N; i ++)
for (int j = 0; j <= 9; j ++)
for (int k = 0; k <= 9; k ++)
if (abs(j - k) >= 2)
f[i][j] += f[i - 1][k];
}
int dp(int n){
if (!n) return 0;
vector <int> nums;
while (n) nums.push_back(n % 10), n /= 10;
int res = 0, last = -2;
for (int i = nums.size() - 1; i >= 0; i --){
int x = nums[i];
for (int j = i == nums.size() - 1; j < x; j ++)
if (abs(j - last) >= 2)
res += f[i + 1][j];
if (abs(x - last) >= 2) last = x;
else break;
if (!i) res ++;
}
for (int i = 1; i < nums.size(); i ++)
for (int j = 1; j <= 9; j ++)
res += f[i][j];
return res;
}
int main(){
init();
int l, r;
scanf("%d %d", &l, &r);
printf("%d", dp(r) - dp(l - 1));
return 0;
}
1084.数字游戏II 题目链接
题目要求各位数字之和 m o d N mod\ N mod N 为 0 0 0,容易想到用额外一维状态表示当前各位数字之和 m o d N mod\ N mod N 的结果。设 f [ i , j , k ] f[i,j,k] f[i,j,k] 表示共有 i i i 位,最高位为 j j j,且各位数字之和 m o d N = k mod\ N=k mod N=k 的数的个数,有:
f [ i , j , k ] = ∑ x = 0 9 f [ i − 1 , x , ( k − x ) m o d N ] f[i,j,k]=\sum\limits_{x=0}^9f[i-1,x,(k-x)\ mod\ N] f[i,j,k]=x=0∑9f[i−1,x,(k−x) mod N]
当前处理到第 i i i 位,且之前各位数字之和为 l a s t last last 时:方案数加上 ∑ x = 0 a i f [ i , x , − l a s t m o d N ] \sum\limits_{x=0}^{a_i}f[i,x,-last\ mod\ N] x=0∑aif[i,x,−last mod N],同时继续枚举下一位数, l a s t + = a i last+=a_i last+=ai。最后特判 n n n 是否满足性质。
代码实现:
#include
#include
#include
using namespace std;
const int N = 11, M = 110;
int P;
int f[N][10][M];
int mod(int x, int y){ //保证取模后的结果是正数
return (x % y + y) % y;
}
void init(){
memset(f, 0, sizeof f);
for (int i = 0; i <= 9; i ++)
f[1][i][i % P] ++;
for (int i = 2; i < N; i ++)
for (int j = 0; j <= 9; j ++)
for (int k = 0; k < P; k ++)
for (int x = 0; x <= 9; x ++)
f[i][j][k] += f[i - 1][x][mod(k - j, P)];
}
int dp(int n){
if (!n) return 1;
vector <int> nums;
while (n) nums.push_back(n % 10), n /= 10;
int res = 0, last = 0;
for (int i = nums.size() - 1; i >= 0; i --){
int x = nums[i];
for (int j = 0; j < x; j ++)
res += f[i + 1][j][mod(-last, P)];
last += x;
if (!i && last % P == 0) res ++;
}
return res;
}
int main(){
int l, r;
while (~scanf("%d %d %d", &l, &r, &P)){
init();
printf("%d\n", dp(r) - dp(l - 1));
}
return 0;
}
1085.不要62 题目链接
代码实现:
#include
#include
#include
using namespace std;
const int N = 11;
int f[N][10];
void init(){
for (int i = 0; i <= 9; i ++)
if (i != 4)
f[1][i] = 1;
for (int i = 2; i < N; i ++)
for (int j = 0; j <= 9; j ++){
if (j == 4) continue;
for (int k = 0; k <= 9; k ++){
if (k == 4 || j == 6 && k == 2) continue;
f[i][j] += f[i - 1][k];
}
}
}
int dp(int n){
if (!n) return 1;
vector <int> nums;
while (n) nums.push_back(n % 10), n /= 10;
int res = 0, last = 0;
for (int i = nums.size() - 1; i >= 0; i --){
int x = nums[i];
for (int j = 0; j < x; j ++){
if (j == 4 || last == 6 && j == 2) continue;
res += f[i + 1][j];
}
if (x == 4 || last == 6 && x == 2) break;
last = x;
if (!i) res ++;
}
return res;
}
int main(){
init();
int l, r;
while(scanf("%d %d", &l, &r), l && r)
printf("%d\n", dp(r) - dp(l - 1));
return 0;
}
1086.恨7不成妻 题目链接
本题的难点不在于数要满足三个性质,而在于题目要求所有满足性质的数的平方和。
考虑在已知共有 i − 1 i-1 i−1 位的所有满足性质的数的平方和时,如何转移至共有 i i i 位且最高位为 j j j 的所有满足性质的数的平方和。设有 i − 1 i-1 i−1 位的满足性质的数有 t t t 个, A k ( 1 ≤ k ≤ t ) A_k\ (1\le k\le t) Ak (1≤k≤t) 是一个有 i − 1 i-1 i−1 位的满足性质的数,那么有:
∑ k = 1 t ( j A k ‾ ) 2 = ∑ k = 1 t ( j × 1 0 i − 1 + A k ) 2 = ∑ k = 1 t A k 2 + 2 × ∑ k = 1 t A k × ( j × 1 0 i − 1 ) + t × ( j × 1 0 i − 1 ) 2 \sum\limits_{k=1}^t(\overline{jA_k})^2=\sum\limits_{k=1}^t(j\times 10^{i-1}+A_k)^2=\sum\limits_{k=1}^tA_k^2+2\times \sum\limits_{k=1}^tA_k\times (j\times 10^{i-1})+t\times (j\times 10^{i-1})^2 k=1∑t(jAk)2=k=1∑t(j×10i−1+Ak)2=k=1∑tAk2+2×k=1∑tAk×(j×10i−1)+t×(j×10i−1)2
其中已知 ∑ k = 1 t A k 2 \sum\limits_{k=1}^tA_k^2 k=1∑tAk2,但仅知道平方和是不够的,我们还需要 ∑ k = 1 t A k \sum\limits_{k=1}^tA_k k=1∑tAk 与 t t t 才能完成状态转移。
基于上述分析,我们可以设计状态 f [ i , j , a , b , s ] f[i,j,a,b,s] f[i,j,a,b,s] 代表共有 i i i 位,最高位为 j j j,数模 7 7 7 余 a a a,各位数字之和模 7 7 7 余 b b b 的所有数的数据 ( s = 0 s=0 s=0 代表个数, s = 1 s=1 s=1 代表和, s = 2 s=2 s=2 代表平方和)。
代码实现:
#include
#include
using namespace std;
typedef long long LL;
const int N = 20, P = 1e9 + 7;
struct F{
int s0, s1, s2;
}f[N][10][7][7];
int power7[N], power9[N];
int mod(LL x, int y){
return (x % y + y) % y;
}
void init(){
for (int i = 0; i <= 9; i ++){
if (i == 7) continue;
auto &v = f[1][i][i % 7][i % 7];
v.s0 ++;
v.s1 += i;
v.s2 += i * i;
}
LL power = 10;
for (int i = 2; i < N; i ++, power *= 10)
for (int j = 0; j <= 9; j ++){
if (j == 7) continue;
for (int a = 0; a < 7; a ++)
for (int b = 0; b < 7; b ++)
for (int k = 0; k <= 9; k ++){
if (k == 7) continue;
auto &v1 = f[i][j][a][b],
&v2 = f[i - 1][k][mod(a - j * power, 7)][mod(b - j, 7)];
v1.s0 = mod(v1.s0 + v2.s0, P);
v1.s1 = mod(v1.s1 + v2.s1 + j * (power % P) % P * v2.s0, P);
v1.s2 = mod(v1.s2 + v2.s2 + 2 * j * (power % P) % P * v2.s1 + j * j * (power % P) % P * (power % P) % P * v2.s0, P);
}
}
power7[0] = power9[0] = 1;
for (int i = 1; i < N; i ++){
power7[i] = power7[i - 1] * 10 % 7;
power9[i] = power9[i - 1] * 10ll % P;
}
}
F get(int i, int j, int a, int b){
int s0 = 0, s1 = 0, s2 = 0;
for (int x = 0; x < 7; x ++)
for (int y = 0; y < 7; y ++){
if (x == a || y == b) continue;
auto v = f[i][j][x][y];
s0 = (s0 + v.s0) % P;
s1 = (s1 + v.s1) % P;
s2 = (s2 + v.s2) % P;
}
return {s0, s1, s2};
}
int dp(LL n){
if (!n) return 0;
LL backup_n = n % P;
vector <int> nums;
while (n) nums.push_back(n % 10), n /= 10;
int res = 0;
LL last_a = 0, last_b = 0;
for (int i = nums.size() - 1; i >= 0; i --){
int x = nums[i];
for (int j = 0; j < x; j ++){
if (j == 7) continue;
int a = mod(-last_a * power7[i + 1], 7);
int b = mod(-last_b, 7);
auto v = get(i + 1, j, a, b);
res = mod(res + (last_a % P) * (last_a % P) % P * power9[i + 1] % P * power9[i + 1] % P * v.s0 % P +
2 * last_a % P * power9[i + 1] % P * v.s1 + v.s2, P);
}
if (x == 7) break;
last_a = last_a * 10 + x;
last_b += x;
if (!i && last_a % 7 && last_b % 7) res = (res + backup_n * backup_n) % P;
}
return res;
}
int main(){
init();
int T;
scanf("%d", &T);
while (T --){
LL l, r;
scanf("%lld %lld", &l, &r);
printf("%d\n", mod(dp(r) - dp(l - 1), P));
}
return 0;
}