[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;
}
原文地址:https://www.cnblogs.com/crashed/p/14555151.html