[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\) 个数码,转移也比较简单:

\[\begin{aligned} g_{i,j}&=\sum_{k=0}^{j}\binom{j}{k}g_{i-1,j-k}\\ f_{i,j}&=\sum_{k=0}^{j}\binom{j}{k}\left(m^{k+l_i}f_{i-1,j-k}+g_{i-1,j-k}\times iV_{k+l_i}\right) \end{aligned} \]

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

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

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

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

设如下几个生成函数:

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

根据 \(G\) 的转移有:

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

根据 \(F\) 的转移有:

\[\begin{aligned} f_{i,j}&=\sum_{k=0}^j\binom{j}{k}(m^{k+l_i}f_{i-1,j-k}+g_{i-1,j-k}\times iV_{k+l_i})\\ \Rightarrow \frac{f_{i,j}x^j}{j!}&=\sum_{k=0}^j\frac{m^kx^k}{k!}\cdot \frac{f_{i-1,j-k}x^{j-k}}{(j-k)!}\cdot m^{l_i}+i\cdot \frac{g_{i-1,j-k}x^{j-k}}{(j-k)!}\cdot\frac{(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_k\prod_{j=k+1}^i p_j \]

考虑答案:

\[\begin{aligned} \frac{ans}{L!} &=[x^L]e^xF_{m-1}(x)\\ &=[x^L]e^x\sum_{k=1}^{m-1}q_k\prod_{j=k+1}^{m-1}p_j\\ &=[x^L]e^x\sum_{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}\right)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}k\left(\prod_{j=k+1}^{m-1}m^{l_j}\right)e^{(m(m-1-k)+k)x}(m^{l_k}V(x)+e^xV_{l_k})\\ &=[x^L]\sum_{k=1}^{m-1}k\left(\prod_{j=k+1}^{m-1}m^{l_j}\right)(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}k\left(\prod_{j=k+1}^{9}10^{l_j}\right)(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(mn\log_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( '\n' );
	return 0;
}
posted @ 2021-03-18 16:19  crashed  阅读(256)  评论(0编辑  收藏  举报