• [HDU6796] X Number


    题目

    点这里看题目。

    分析

    显然可以数位 DP 。

    (R) 的位数比较小的时候,我们可以暴力搜索出所有数字的出现情况,然后进行 DP 。

    但是当 (R) 很长的时候,状态的范围就会非非非常大,无法 DP 。

    但是注意到另一个事实是:对于一个确定的数,我们并不需要知道它长什么样子,而只需要知道数字的出现相对次数和对应的 (D)

    这意味着,某些数对于我们而言是相同的。

    比如 499892322624 对于我们而言,就是相同的,因为数码的出现情况是相同的。

    这提示我们,像这样的数可以直接归为一类来计算。换言之,我们可以对它们重编号

    其实随便怎么编号都无所谓,为了方便,我们统一采取 " 字典序最大 " 的方法(注意我们的 (D) 也有可能会变化)。

    比如 499892322624 就都相当于 988786 ,这样只计算一次就可以算出许多数的方案。

    采用这个方法, DP 状态就能得到极大的优化。反映到实际运行上,就相当于是:

     T 飞了 => 差 1~2s 就能卡进时限了!
    

    嗯 ...... 于是我们还是会 TLE 。

    考虑继续优化。注意到,当我们选数不再受上界影响的时候,这个问题实际上就会变得简单一些。

    因此,当选数没有上界时,我们就可以直接背包 + 组合数解决弱化的问题。

    然后存在上界的时候,我们再进行常规的数位 DP 计算。

    这样就可以跑过了。

    本题一些有价值的点:

    1. 优化状态。本质上忽略数码本身,而只考虑数码的出现情况
    2. 特殊哈希方法。这里没有采用进制压缩,而是使用了 " 字典序最大 " 的方法,将同一类出现情况映射到同一个数上。
    3. 灵活应变。问题复杂的时候,使用正常的 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;
    }
    
  • 相关阅读:
    sharepoint的webpart开发
    触发器-插入不重复数据2
    触发器-插入不重复数据
    InfoPath本地发布及部署
    从30岁到35岁:为你的生命多积累一些厚度
    js中的forin
    js中的prototye
    无法绑定由多个部分组成的标示符
    Spring注入方式及用到的注解
    ( 转)mappingResource属性和mappingDirectoryLocations属性的使用
  • 原文地址:https://www.cnblogs.com/crashed/p/13553651.html
Copyright © 2020-2023  润新知