题目描述
难度分:2197
输入长度≤2×105的字符串s和整数k(1≤k≤16)。
s表示一个十六进制数,无前导零。
在[1,s]中,有多少个(无前导零的)十六进制数,恰好包含k个不同的数字?这里的数字指0~9和A
~F
。
答案模109+7。
输入样例1
10 1
输出样例1
15
输入样例2
FF 2
输出样例2
225
输入样例3
100 2
输出样例3
226
输入样例4
1A8FD02 4
输出样例4
3784674
输入样例5
DEADBEEFDEADBEEEEEEEEF 16
输出样例5
153954073
算法
数位DP
按照数位DP
的套路来做,从高位往低位填数。
状态定义
f[i][mask][islimit][isnum]表示填到i这个数位,之前所填数位的数字组成了mask这个二进制状态,前面数字贴合上界情况为islimit(如果前面填的数贴合上界s的数位则有islimit=1),当前是不是一个数字都没填isnum(填了数就满足isnum=1)。这里如果直接让第二维是二进制数mask肯定是会爆空间的,我们可以只存mask中1的数目,这样就让第二维的大小从2k变为k。
状态转移
对于边界情况,当所有数位都填完时,只要isnum=1并且mask中1的数量是k,则产生了一个合法的数字。
对于一般情况,尝试当前位填的数字d∈[0,ub],如果islimit=1,那么ub=s[i],否则ub=15。
- 如果isnum=0,那么就还是一个数都没填,状态转移方程为f[i][popcount(mask)][islimit][isnum]=f[i+1][popcount(mask)][0][isnum]。
- 如果填了数字d,那么二进制状态mask就会变为mask∨2d,状态转移方程为f[i][popcount(mask)][islimit][isnum]=Σubd=1−isnumf[i+1][popcount(mask∨2d)][islimit∧d=ub][1]。
其中popcount(num),表示num在二进制下有多少个1。
复杂度分析
时间复杂度
状态数量为O(nk)。单次转移需要遍历d∈[0,ub],可以看成是O(k)。所以整个算法的时间复杂度为O(nk2)。
空间复杂度
空间消耗的瓶颈就是状态数组,额外空间复杂度为状态数量O(nk)。
C++ 代码
#include <bits/stdc++.h>
using namespace std;
const int MOD = 1e9 + 7, N = 200010;
string s;
int k, n, f[N][16][2][2];
int dfs(int i, int mask, int islimit, int isnum) {
if(i == n) {
return f[i][__builtin_popcount(mask)][islimit][isnum] = (isnum && __builtin_popcount(mask) == k)? 1: 0;
}
int &v = f[i][__builtin_popcount(mask)][islimit][isnum];
if(v != -1) return v;
int cnt = !isnum? dfs(i + 1, mask, 0, 0): 0;
int digit = isdigit(s[i])? s[i] - '0': s[i] - 'A' + 10;
int ub = islimit? digit: 15;
for(int d = 1 - isnum; d <= ub; d++) {
cnt = (cnt + dfs(i + 1, mask|(1<<d), islimit && d == ub, 1)) % MOD;
}
return v = cnt;
}
int main() {
cin >> s >> k;
n = s.size();
memset(f, -1, sizeof f);
cout << dfs(0, 0, 1, 0) << endl;
return 0;
}