题目
点这里看题目。
分析
显然可以数位 DP 。
当 (R) 的位数比较小的时候,我们可以暴力搜索出所有数字的出现情况,然后进行 DP 。
但是当 (R) 很长的时候,状态的范围就会非非非常大,无法 DP 。
但是注意到另一个事实是:对于一个确定的数,我们并不需要知道它长什么样子,而只需要知道数字的出现相对次数和对应的 (D) 。
这意味着,某些数对于我们而言是相同的。
比如 499892
和 322624
对于我们而言,就是相同的,因为数码的出现情况是相同的。
这提示我们,像这样的数可以直接归为一类来计算。换言之,我们可以对它们重编号。
其实随便怎么编号都无所谓,为了方便,我们统一采取 " 字典序最大 " 的方法(注意我们的 (D) 也有可能会变化)。
比如 499892
和 322624
就都相当于 988786
,这样只计算一次就可以算出许多数的方案。
采用这个方法, DP 状态就能得到极大的优化。反映到实际运行上,就相当于是:
T 飞了 => 差 1~2s 就能卡进时限了!
嗯 ...... 于是我们还是会 TLE 。
考虑继续优化。注意到,当我们选数不再受上界影响的时候,这个问题实际上就会变得简单一些。
因此,当选数没有上界时,我们就可以直接背包 + 组合数解决弱化的问题。
然后存在上界的时候,我们再进行常规的数位 DP 计算。
这样就可以跑过了。
本题一些有价值的点:
- 优化状态。本质上忽略数码本身,而只考虑数码的出现情况。
- 特殊哈希方法。这里没有采用进制压缩,而是使用了 " 字典序最大 " 的方法,将同一类出现情况映射到同一个数上。
- 灵活应变。问题复杂的时候,使用正常的 DP ;而问题弱化的时候,就使用更快速的方法。
代码
#include <cstdio>
#include <cstring>
#include <utility>
using namespace std;
typedef long long LL;
typedef pair<LL, int> pii;
#define int LL
const int mod = 501157, MAXN = 1e6 + 5;
template<typename _T>
void read( _T &x )
{
x = 0;char s = getchar();int f = 1;
while( s > '9' || s < '0' ){if( s == '-' ) f = -1; s = getchar();}
while( s >= '0' && s <= '9' ){x = ( x << 3 ) + ( x << 1 ) + ( s - '0' ), s = getchar();}
x *= f;
}
template<typename _T>
void write( _T x )
{
if( x < 0 ){ putchar( '-' ); x = ( ~ x ) + 1; }
if( 9 < x ){ write( x / 10 ); }
putchar( x % 10 + '0' );
}
LL pw[20];
int upper[20], tmp[20];
int C[20][20];
int D;
struct HashTable
{
pii rVal[MAXN];
int ans[MAXN][10];
int head[mod], nxt[MAXN];
int siz = 0;
LL DP( LL num, const int lef, const int d )
{
LL ret = 0;
LL f[11][20] = {}; int cnt[10] = {};
while( num ) cnt[num % 10] ++, num /= 10;
for( int t = 0 ; t <= lef ; t ++ )
{
memset( f, 0, sizeof f );
f[10][0] = C[lef][t];
for( int i = 9 ; ~ i ; i -- )
{
if( i == d )
{
for( int j = 0 ; j <= lef ; j ++ )
f[i][j] = f[i + 1][j];
}
else
{
for( int j = 0 ; j <= lef - t ; j ++ )
for( int k = 0 ; k <= lef - j - t && k + cnt[i] < t + cnt[d] ; k ++ )
f[i][j + k] += f[i + 1][j] * C[lef - j - t][k];
}
}
ret += f[0][lef - t];
}
return ret;
}
LL query( LL num, const int lef, const int d )
{
pii key( num, lef );
int HASH = ( num % mod * 20 % mod + lef ) % mod;
for( int i = head[HASH] ; i ; i = nxt[i] )
if( key == rVal[i] )
return ~ ans[i][d] ? ans[i][d] : ( ans[i][d] = DP( num, lef, d ) );
int cur = ++ siz;
memset( ans[cur], -1, sizeof ans[cur] );
nxt[cur] = head[HASH], rVal[cur] = key, head[HASH] = cur;
return ans[cur][d] = DP( num, lef, d );
}
}T;
void init()
{
pw[0] = 1;
for( int i = 1 ; i <= 18 ; i ++ )
pw[i] = pw[i - 1] * 10;
for( int i = 0 ; i < 20 ; i ++ )
{
C[i][0] = C[i][i] = 1;
for( int j = 1 ; j < i ; j ++ )
C[i][j] = C[i - 1][j] + C[i - 1][j - 1];
}
}
int split( LL num, int *ret )
{
int len = 0;
for( int i = 0 ; i < 20 ; i ++ ) ret[i] = 0;
while( num ) ret[len ++] = num % 10, num /= 10;
return len;
}
LL DFS( const int ind, LL val, const bool up, const int D )
{
LL ret = 0;
if( ind < 0 || ! up )
{
if( ! val )
{
if( ind < 0 ) return D == 0;
for( int i = 0 ; i < 10 ; i ++ )
ret += DFS( ind - 1, val + pw[ind] * i, false, D );
}
else
{
int mp[10]; memset( mp, -1, sizeof mp ); mp[0] = 0;
for( int i = 0 ; i <= ind ; i ++ ) val /= 10;
int L = split( val, tmp ), dig = 9; LL nw = 0;
for( int i = L - 1 ; ~ i ; i -- )
{
if( ! ( ~ mp[tmp[i]] ) ) mp[tmp[i]] = dig --;
nw = nw + pw[i] * mp[tmp[i]];
}
for( int i = 9 ; ~ i ; i -- )
if( ! ( ~ mp[i] )) mp[i] = dig --;
ret = T.query( nw, ind + 1, mp[D] );
}
return ret;
}
for( int i = 0 ; i <= upper[ind] ; i ++ )
ret += DFS( ind - 1, val + pw[ind] * i, i == upper[ind], D );
return ret;
}
LL calc( const LL lim )
{
int len = split( lim, upper );
return DFS( len - 1, 0, true, D );
}
signed main()
{
init();
int T;
LL L, R;
read( T );
while( T -- )
{
read( L ), read( R ), read( D );
write( calc( R ) - calc( L - 1 ) ), putchar( '
' );
}
return 0;
}