zoukankan      html  css  js  c++  java
  • [CF908G] New Year and Original Order

    题目

    点这里看题目。

    分析

    看见大家都写数位 DP ,可是我就是不会数位 DP 。

    为了方便描述,不妨设 (m) 为进制,在这个问题中 (m=10)

    类似于数位 DP ,我们可以限制当前的数在前 (i) 位与 (X) 相同,且在第 (i+1) 位比 (X) 小,那么在更低的位就没有限制了,我们可以方便地统计。

    此时的问题就相当于是,我们仍然选 (n) 个数码,但在确定的前缀中,对于数码 (k) ,它至少会被选择 (l_k) 次,并且这 (l_k) 个的位置已经确定的。

    那么不难想到如下 DP :

    (g_{i,j}) 表示考虑了前 (i)非零数码后,已有的数字的总长度为 (j)方案数

    (f_{i,j}) 表示考虑了前 (i)非零数码后,已有的数字的总长度为 (j)所有 (f(X)) 的和

    另设 (V_k) 表示长度为 (k) 的全是 1 的数的值,即 (frac{m^k-1}{m-1})

    假设现在未被确定的有 (L) 个数码,转移也比较简单:

    [egin{aligned} g_{i,j}&=sum_{k=0}^{j}inom{j}{k}g_{i-1,j-k}\ f_{i,j}&=sum_{k=0}^{j}inom{j}{k}left(m^{k+l_i}f_{i-1,j-k}+g_{i-1,j-k} imes iV_{k+l_i} ight) end{aligned} ]

    最后我们不需要考虑 0 的贡献,位置也不重要,所以:

    [ans=sum_{k=0}^{L}inom{L}{k}f_{m-1,k} ]

    现在我们就得到了 (O(m^2n^3)) 的算法。

    考虑优化这个过程。由于 (f,g) 的转移比较简单,我们考虑推导生成函数。

    设如下几个生成函数:

    [egin{aligned} G_j(x)&=sum_{0le k}frac{g_{j,k}x^k}{k!}\ F_j(x)&=sum_{0le k}frac{f_{j,k}x^k}{k!}\ V(x)&=sum_{0le k}frac{V_kx^k}{k!} end{aligned} ]

    根据 (G) 的转移有:

    [G_k(x)=e^xcdot G_{k-1}(x)Rightarrow G_{k}(x)=e^{kx} ]

    根据 (F) 的转移有:

    [egin{aligned} f_{i,j}&=sum_{k=0}^jinom{j}{k}(m^{k+l_i}f_{i-1,j-k}+g_{i-1,j-k} imes iV_{k+l_i})\ Rightarrow frac{f_{i,j}x^j}{j!}&=sum_{k=0}^jfrac{m^kx^k}{k!}cdot frac{f_{i-1,j-k}x^{j-k}}{(j-k)!}cdot m^{l_i}+icdot frac{g_{i-1,j-k}x^{j-k}}{(j-k)!}cdotfrac{(m^{l_i}V_k+V_{l_i})x^k}{k!}\ Rightarrow F_i(x)&=m^{l_i}cdot e^{mx}F_{i-1}(x)+iG_{i-1}(x)(m^{l_i}V(x)+e^xV_{l_i})\ Rightarrow F_i(x)&=m^{l_i}cdot e^{mx}F_{i-1}(x)+ie^{(i-1)x}(m^{l_{i}}V(x)+e^xV_{l_i}) end{aligned} ]

    (p_i=m^{l_i}e^{mx},q_i=ie^{(i-1)x}(m^{l_i}V(x)+e^xV_{l_i})) ,则有:

    [F_i(x)=sum_{k=1}^iq_kprod_{j=k+1}^i p_j ]

    考虑答案:

    [egin{aligned} frac{ans}{L!} &=[x^L]e^xF_{m-1}(x)\ &=[x^L]e^xsum_{k=1}^{m-1}q_kprod_{j=k+1}^{m-1}p_j\ &=[x^L]e^xsum_{k=1}^{m-1}ke^{(k-1)x}(m^{l_k}V(x)+e^xV_{l_k})prod_{j=k+1}^{m-1}m^{l_j}e^{mx}\ &=[x^L]sum_{k=1}^{m-1}left(prod_{j=k+1}^{m-1}m^{l_j} ight)e^{m(m-1-k)x}cdot ke^{kx}(m^{l_k}V(x)+e^xV_{l_k})\ &=[x^L]sum_{k=1}^{m-1}kleft(prod_{j=k+1}^{m-1}m^{l_j} ight)e^{(m(m-1-k)+k)x}(m^{l_k}V(x)+e^xV_{l_k})\ &=[x^L]sum_{k=1}^{m-1}kleft(prod_{j=k+1}^{m-1}m^{l_j} ight)(m^{l_k}e^{(m(m-1-k)+k)x}V(x)+e^{(m(m-1-k)+k+1)x}V_{l_k})\ end{aligned} ]

    看到特殊的情况,即带入 (m=10)

    [ans=L![x^L]sum_{k=1}^{9}kleft(prod_{j=k+1}^{9}10^{l_j} ight)(10^{l_k}e^{(90-9k)x}V(x)+e^{(91-9k)x}V_{l_i}) ]

    考虑计算这个式子,可以发现唯一的问题就在于 (e^{(90-9k)x}V(x)) 比较复杂。不过,由于 (k) 的值很少,我们可以直接枚举 (k) ,预处理 (e^{(90-9k)x}V(x)) 的结果。这样我们就可以 (O(m)) 计算一次 (ans)

    这里 (V(x)) 可以得到一个闭形式: (V(x)=frac{1}{m-1}(e^{mx}-e^x)=frac{1}{m-1}e^x(e^{(m-1)x}-1)) ,那么可以得到闭形式为 (frac{1}{m-1}e^{(m(m-1-k)+k)x}e^x(e^{(m-1)x}-1)) ,这个东西就可以 (O(1)) 计算了。

    时间复杂度是 (O(mn^2)) ,如果你喜欢,也可以变成 (O(mnlog_2n)) ,甚至 (O(m^2n))

    小结:

    数学 无脑直接推 练习题,还是 Tiw 的做法更简单。

    代码

    点我查看代码
    #include <cstdio>
    #include <cstring>
    
    #define rep( i, a, b ) for( int i = (a) ; i <= (b) ; i ++ )
    #define per( i, a, b ) for( int i = (a) ; i >= (b) ; i -- )
    
    const int mod = 1e9 + 7;
    const int MAXN = 700 + 5, MAXK = 10;
    
    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' );
    }
    
    template<typename _T>
    _T MAX( const _T a, const _T b )
    {
    	return a > b ? a : b;
    }
    
    int low[MAXK];
    
    int fac[MAXN], ifac[MAXN];
    
    int V[MAXN], pw[MAXN], spw[MAXN];
    
    int X[MAXN];
    char str[MAXN];
    int N, ans = 0, inv9;
    
    inline int Qkpow( int, int );
    inline int Mul( int x, int v ) { return 1ll * x * v % mod; }
    inline int Inv( const int a ) { return Qkpow( a, mod - 2 ); }
    inline int Sub( int x, int v ) { return ( x -= v ) < 0 ? x + mod : x; }
    inline int Add( int x, int v ) { return ( x += v ) >= mod ? x - mod : x; }
    
    int Qkpow( int base, int indx )
    {
    	int ret = 1;
    	while( indx )
    	{
    		if( indx & 1 ) ret = Mul( ret, base );
    		base = Mul( base, base ), indx >>= 1;
    	}
    	return ret;
    }
    
    void Init()
    {
    	spw[0] = pw[0] = 1, V[0] = 0;
    	rep( i, 1, N ) pw[i] = Mul( pw[i - 1], 10 ), V[i] = Add( V[i - 1], pw[i - 1] );
    	inv9 = Inv( 9 );	
    	fac[0] = 1; rep( i, 1, N ) fac[i] = Mul( fac[i - 1], i );
    	ifac[N] = Inv( fac[N] ); per( i, N - 1, 0 ) ifac[i] = Mul( ifac[i + 1], i + 1 );
    }
    
    int Calc( const int L )
    {
    	int ret = 0, prod = 1;
    	per( k, 9, 1 )
    	{
    		int coe = Mul( inv9, Sub( Mul( Qkpow( 100 - 9 * k, L ), ifac[L] ), Mul( Qkpow( 91 - 9 * k, L ), ifac[L] ) ) );
    		ret = Add( ret, Mul( Mul( prod, k ), Add( Mul( coe, pw[low[k]] ), Mul( V[low[k]], Mul( Qkpow( 91 - 9 * k, L ), ifac[L] ) ) ) ) );
    		prod = Mul( prod, pw[low[k]] );
    	}
    	return Mul( ret, fac[L] );
    }
    
    int main()
    {
    	scanf( "%s", str + 1 ), N = strlen( str + 1 );
    	rep( i, 1, N ) X[N - i + 1] = str[i] - '0';
    	Init();
    	per( i, N, 1 )
    	{
    		rep( j, 0, X[i] - 1 )
    		{
    			low[j] ++; 
    			ans = Add( ans, Calc( i - 1 ) ); 
    			low[j] --;
    		}
    		low[X[i]] ++;
    	}
    	ans = Add( ans, Calc( 0 ) );
    	write( ans ), putchar( '
    ' );
    	return 0;
    }
    
  • 相关阅读:
    Python之pytest 基础
    unittest和pytest的区别
    Selenium 常用定位对象元素的方法
    ORCAl存储过程
    Mysql的存储过程
    TestNG 搭建测试框架 自动化测试
    通过junit/TestNG+java 实现自动化测试
    查看APP用到的图片方法
    码农干私活的建议(转)
    Android的onCreateOptionsMenu()创建菜单Menu详解(转)
  • 原文地址:https://www.cnblogs.com/crashed/p/14555151.html
Copyright © 2011-2022 走看看