记忆化搜索
既然需要出现的次数 \(\geq\) 长度的一半,我们不妨就枚举这个数,按照记搜的套路,我们记录一下这个数的出现次数以及是否没了前导零即可。
记录次数的时候如果往后添的数是枚举的数,则 \(++cnt\) ,否则 \(--cnt\) ,易证符合条件当且仅当 \(pos=0\) 时 \(cnt\) \(\geq\) \(0\).
我们发现对于 \(10\) , \(1122\) ,\(2323\) 这类数会被计算 \(2\) 次,我们再用类似的方法减去即可。
注意数组下标不能是负数。
详细可以看代码。
#include<iostream>
#include<cstring>
#define LL long long
using namespace std;
int num1, num2, tot;
LL l, r;
int w[233];
LL f[20][40][40][2][2];
LL dfs(int pos, int cnt1, int cnt2, int pre, int g)
{
if (!pos)return pre && (num2 == -1 ? cnt1 >= 19 : (cnt1 >= 19 && cnt2 >= 19));
if (f[pos][cnt1][cnt2][pre][g] != -1)return f[pos][cnt1][cnt2][pre][g];
int lim = g ? w[pos] : 9 , t1, t2; LL res = 0;
for (int i = 0; i <= lim; ++i)
t1 = cnt1 + (pre || i > 0) * (i == num1 ? 1 : -1),
t2 = num2 == -1 ? 0 : cnt2 + (pre || i > 0) * (i == num2 ? 1 : -1),
res += dfs(pos - 1, t1, t2 , pre || (i > 0), g && i == lim);
return f[pos][cnt1][cnt2][pre][g] = res;
}
LL solve(LL x)
{
tot = 0;
while (x)w[++tot] = x % 10, x /= 10;
LL res = 0;
for (int i = 0; i <= 9; ++i)
memset(f, -1, sizeof(f)), num1 = i, num2 = -1, res += dfs(tot, 19, 0, 0, 1);
for (int i = 0; i <= 9; ++i)
for (int j = i + 1; j <= 9; ++j)
memset(f, -1, sizeof(f)), num1 = i, num2 = j, res -= dfs(tot, 19, 19, 0, 1);
return res;
}
signed main()
{
cin >> l >> r;
cout << solve(r) - solve(l - 1);
return 0;
}