前导零注意(只需要一个标记)
【数位特点】x连号、有/没有x
二分答案
【数位特点】x的倍数、有/没有x
前导零再注意(占dp中的一维)
【数位特点】有子串为xyz
另:【异或和】的意思就是所有答案异或
之前的题都是统计数的个数,这一题统计数码出现的次数
【数位特点】相邻数位的差有限制
【数位特点】各个位之积为x
前面问个数,这道题问的是符合条件的数的和
超时版(ac 20%)原因在于这个代码中,对应不同K的dp数组不能重用,而数据组数最大是5000,就会超时
# include <cstdio>
# include <cstring>
# include <unordered_map>
typedef long long i64;
using namespace std;
const i64 p = 20120427;
struct Node{ i64 sum, cnt;};
Node dp[20][2][36102]; //第三维本来应该是9^18,进行映射
int b[20];
i64 m1[36102], m_cnt = 0;
i64 pow10[20];
unordered_map <i64, int >m2;
i64 K;
void init(void) //映射,用bfs更简单?
{
pow10[0] = 1;
for (int i=1; i<=19; i++) pow10[i] = 10*pow10[i-1];
int L, R, lastL, lastR;
m1[++m_cnt] = 0; m2[0] = m_cnt;
for (int i=1; i<=9; i++) {m1[++m_cnt] = i; m2[i] = m_cnt;}
lastL = 2, lastR = m_cnt;
for (int i=2; i<=18; i++) //最多乘18次
{
L = lastL, R = lastR, lastL = R+1;
for (int j=L; j<=R; j++)
for (int k=2; k<=9; k++) //每次乘2~9
if (m2[m1[j]*k] == 0) {m1[++m_cnt] = m1[j]*k; m2[m1[j]*k] = m_cnt;}
lastR = m_cnt;
}
//for (int i=1; i<=50; i++) printf("%lld ", m1[i]);
}
Node dfs(int n, bool qiandao0, int mpl_inx, bool ismax)
{
if (n == 0)
{
if (m1[mpl_inx] == K) return Node{0, 1};
else return Node{0, 0};
}
if (!ismax && dp[n][qiandao0][mpl_inx].sum != -1) return dp[n][qiandao0][mpl_inx];
int m = ismax ? b[n] : 9;
Node ans, tmp;
ans.cnt = 0;
ans.sum = 0;
for (int i = 0; i <= m; i++)
{
if (qiandao0)
{
if (i == 0) tmp = dfs(n-1, true, m2[1], ismax&&i==m);
else tmp = dfs(n-1, false, m2[i], ismax&&i==m);
}
else tmp = dfs(n-1, false, m2[m1[mpl_inx]*i], ismax&&i==m);
ans.cnt += tmp.cnt;
//if (tmp.cnt>0) printf("%d位 枚举i=%d cnt=%lld\n", n, i, tmp.cnt);
ans.sum = (ans.sum + tmp.sum + (i*tmp.cnt)%p*pow10[n-1]%p)%p;
//printf("%lld\n", ans.sum);
}
if (!ismax) dp[n][qiandao0][mpl_inx] = ans;
return ans;
}
int split(i64 n)
{
int bit = 0;
while (n > 0)
{
b[++bit] = n % 10;
n /= 10;
}
return bit;
}
int main(void)
{
init();
i64 L, R, t;
scanf("%lld", &t);
while (t--)
{
memset(dp, -1, sizeof(dp)); //超时的原因
scanf("%lld%lld%lld", &L, &R, &K);
printf("%lld\n", ((dfs(split(R), true, m2[1], true).sum-dfs(split(L-1), true, m2[1], true).sum)%p+p)%p);
}
return 0;
}
那怎么让对于给定不同的数位之积算得的dp数组可以重复利用呢?
就是带着K算,最后不是检查数位之积是否为K,而是每枚举一位i就用K除以i,检查最后是否剩下的是1。当然,这种情况里,0就要单独考虑了。
# include <cstdio>
# include <cstring>
# include <unordered_map>
typedef long long i64;
using namespace std;
const i64 p = 20120427;
struct Node{ i64 sum, cnt;};
int b[20];
i64 pow10[20];
// 对于K!=0
Node dp[20][2][36102]; //第三维本来应该是9^18
i64 m1[36102], m_cnt = 0;
unordered_map <i64, int >m2;
// 对于K==0
Node dpk0[20][2][2];
void init(void) //映射,用bfs更简单?
{
pow10[0] = 1; for (int i=1; i<=19; i++) pow10[i] = 10*pow10[i-1];
int L, R, lastL, lastR;
m1[++m_cnt] = 0; m2[0] = m_cnt;
for (int i=1; i<=9; i++) { m1[++m_cnt] = i; m2[i] = m_cnt; }
lastL = 2, lastR = m_cnt;
for (int i=2; i<=18; i++){
L = lastL, R = lastR, lastL = R+1;
for (int j=L; j<=R; j++)
for (int k=2; k<=9; k++)
if (m2[m1[j]*k] == 0) { m1[++m_cnt] = m1[j]*k; m2[m1[j]*k] = m_cnt; }
lastR = m_cnt;
}
}
Node dfs_Kis0(int n, bool qiandao0, bool have0, bool ismax)
{
if (n == 0) return Node{0, have0};
if (!ismax && dpk0[n][qiandao0][have0].sum != -1) return dpk0[n][qiandao0][have0];
int m = ismax ? b[n] : 9;
Node ans = {0, 0}, tmp;
for (int i = 0; i <= m; i++)
{
tmp = dfs_Kis0(n-1, qiandao0&&i==0, have0||(i==0 && !qiandao0), ismax&&i==m);
ans.cnt += tmp.cnt;
//if (tmp.cnt>0) printf("%d位 枚举i=%d cnt=%lld\n", n, i, tmp.cnt);
ans.sum = (ans.sum + tmp.sum + (i*tmp.cnt)%p*pow10[n-1]%p)%p;
//printf("%lld\n", ans.sum);
}
if (!ismax) dpk0[n][qiandao0][have0] = ans;
return ans;
}
Node dfs_Knot0(int n, bool qiandao0, int res_mpl, bool ismax)
{
if (n == 0){
if (m1[res_mpl] == 1) return Node{0, 1};
else return Node{0, 0};
}
if (!ismax && dp[n][qiandao0][res_mpl].sum != -1) return dp[n][qiandao0][res_mpl];
int m = ismax ? b[n] : 9;
Node ans = {0, 0}, tmp;
for (int i = 0; i <= m; i++)
{
if (!qiandao0 && i==0) continue; //K不等于0时,枚举的数位除了前导0,不可以有0
if (qiandao0 && i==0) tmp = dfs_Knot0(n-1, true, res_mpl, ismax&&i==m);
else if (m1[res_mpl] % i == 0) tmp = dfs_Knot0(n-1, false, m2[m1[res_mpl]/i], ismax&&i==m);
else continue;
ans.cnt += tmp.cnt;
//if (tmp.cnt>0) printf("%d位 枚举i=%d cnt=%lld\n", n, i, tmp.cnt);
ans.sum = (ans.sum + tmp.sum + (i*tmp.cnt)%p*pow10[n-1]%p)%p;
//printf("%lld\n", ans.sum);
}
if (!ismax) dp[n][qiandao0][res_mpl] = ans;
return ans;
}
int split(i64 n)
{
int bit = 0;
while (n > 0) {
b[++bit] = n % 10;
n /= 10;
}
return bit;
}
int main(void)
{
init();
i64 L, R,K, t;
scanf("%lld", &t);
memset(dp, -1, sizeof(dp));
memset(dpk0, -1, sizeof(dpk0));
while (t--)
{
scanf("%lld%lld%lld", &L, &R, &K);
if (K)
printf("%lld\n", ((dfs_Knot0(split(R), true, m2[K], true).sum-dfs_Knot0(split(L-1), true, m2[K], true).sum)%p+p)%p);
else //K==0时,就是统计含有数字0的数的和有几个,用函数dfs2计算
printf("%lld\n", ((dfs_Kis0(split(R), true,false ,true).sum-dfs_Kis0(split(L-1), true,false, true).sum)%p+p)%p);
}
return 0;
}
突然想起一道不是给定各个位之积,而是给定各个位之lcm的题目。后补。
最后,数位dp的模板
# include <cstdio>
# include <cstring>
typedef long long i64;
using namespace std;
i64 dp[________];
int b[__];
i64 dfs(int n, ..., bool ismax)
{
if (n == 0)
{
...
}
if (!ismax && dp[________] != -1) return dp[________];
int m = ismax ? b[n] : 9;
i64 ans = 0;
for (int i = 0; i <= m; i++)
{
...
}
if (!ismax) dp[________] = ans;
return ans;
}
int split(i64 n)
{
int bit = 0;
while (n > 0)
{
b[++bit] = n % 10;
n /= 10;
}
return bit;
}
int main(void)
{
memset(dp, -1, sizeof(dp));
i64 L, R;
scanf("%lld%lld", &L, &R);
printf("%lld\n", dfs(split(R), ..., true)-dfs(split(L-1), ..., true));
return 0;
}