1. 程式人生 > 實用技巧 >Solution -「LOCAL」充電

Solution -「LOCAL」充電

\(\mathcal{Description}\)

  給定 \(n,m,p\),求序列 \(\{a_n\}\) 的數量,滿足 \((\forall i\in[1,n])(a_i\in[1,m])\land(\forall i\in(1,n])(a_{i-1}\le a_i)\land\left(\sum_{i=1}^na_i10^{n-i}\bmod p=0\right)\),對 \(998244353\) 取模。

  \(n\le10^{18}\)\(m\le50\)\(p\le200\)

\(\mathcal{Solution}\)

  肯定要利用 \(10^x\)\(\bmod p\) 意義下存在迴圈節的性質來求解。考場上想到暴力掃前 \(\mathcal O(p)\)

個 DP 狀態然後用倍增來求解,不過死掉了 qwq。

  轉化題意——因為序列單調不減,記 \(1_n=\underbrace{11\cdots1}_{n\text{個}1}\),那麼任意一種序列所對應的 \(\sum_{i=1}^na_i10^{n-i}\) 都可以由若干個 \(1_x\) 相加得到。為了保證 \(a_i\ge1\),我們欽定一個 \(1_n\) 計入貢獻。而顯然 \(1_x\)\(\bmod p\) 意義下亦存在迴圈節,我們可以求出 \(c_i\) 表示有 \(c_i\)\(1_x\bmod p=i~(x\in[1,n])\)。問題就轉化成:有 \(p\) 物品,第 \(i\)

類物品權值為 \(i\),有 \(c_i\) ,每種有無數個,求選出 \(m-1\) 物品使得其權值和\(\bmod p=0\) 的方案數。

  接下來類似揹包 DP,定義 \(f(i,j,k)\) 表示決定了前 \(i\) 類物品,已選了 \(j\) 個,權值和\(\bmod p=k\) 的方案數。轉移列舉第 \(i+1\) 類所選個數 \(t\),用隔板法計算方案,有:

\[f(i+1,j+t,(k+t(i+1))\bmod p)\leftarrow f(i,j,k)\binom{c_{i+1}+t-1}{c_{i+1}-1} \]

  特別留意初始狀態應為 \(f(-1,0,1_n\bmod p)=1\)

,因為種類編號從 \(0\) 開始;預先欽定了一個 \(1_n\)

  複雜度 \(\mathcal O(m^2p^2)\)

\(\mathcal{Code}\)

#include <cstdio>

typedef long long LL;
#define int LL

const int MOD = 998244353, MAXM = 50, MAXP = 200;
LL n, buc[MAXP + 5];
int m, p, visc[MAXP + 5], suf[MAXP + 5], inv[MAXM + 5];
int f[2][MAXM + 5][MAXP + 5];

inline void addeq ( int& a, const int b ) { if ( ( a += b ) >= MOD ) a -= MOD; }

inline void total ( const int fir, const int cnt ) {
	bool onc[MAXP + 5] = {};
	LL cirs = cnt - visc[fir], cirt = n - cnt + cirs + 1;
	for ( int i = fir, stp = 1; stp <= cirs; i = suf[i], ++ stp ) {
		onc[i] = true;
		buc[i] = cirt / cirs + ( stp <= cirt % cirs );
		if ( stp % cirs == cirt % cirs ) f[0][0][i] = 1;//, printf ( "!%lld\n", i );
	}
	for ( int i = 1 % p, stp = 1; !onc[i] && stp <= n; i = suf[i], ++ stp ) {
		buc[i] = 1;
		if ( n == stp ) f[0][0][i] = 1;//, printf ( "!%lld\n", i );
	}
}

signed main () {
	freopen ( "charge.in", "r", stdin );
	freopen ( "charge.out", "w", stdout );
	scanf ( "%lld %lld %lld", &n, &m, &p ), -- m;
	for ( int i = 0; i < p; ++ i ) visc[i] = -1;
	for ( int l = 1, sum = 1 % p, pwr = 10 % p; ; pwr = 10 * pwr % p, ++ l ) {
		if ( ~visc[sum] ) { total ( sum, l ); break; }
		visc[sum] = l, suf[sum] = ( sum + pwr ) % p;
		if ( l == n ) { total ( sum, l ); break; }
		sum = ( sum + pwr ) % p;
	}
	inv[1] = 1;
	for ( int i = 2; i <= m; ++ i ) {
		inv[i] = 1ll * ( MOD - MOD / i ) * inv[MOD % i] % MOD;
	}
	for ( int i = -1, sta = 0; i < p - 1; ++ i, sta ^= 1 ) {
		for ( int j = 0; j <= m; ++ j ) {
			for ( int k = 0; k < p; ++ k ) {
				f[sta ^ 1][j][k] = 0;
			}
		}
		for ( int k = 0; k < p; ++ k ) {
			for ( int t = 0; t <= m; ++ t ) {
                                // 留意列舉的順序,本質上是f(i,j,k)向後轉移,
                                // 但這裡統一計算了t相等時的組合數,再一起轉移。
				int c = 1;
				for ( LL u = buc[i + 1] + t - 1, v = t; v; -- u, -- v ) {
					c = 1ll * c * ( u % MOD ) % MOD * inv[v] % MOD;
				}
				for ( int j = 0; j + t <= m; ++ j ) {
					addeq ( f[sta ^ 1][j + t][( k + ( i + 1 ) * t ) % p],
						1ll * f[sta][j][k] * c % MOD );
				}
			}
		}
	}
	int ans = 0;
	for ( int i = 0; i <= m; ++ i ) addeq ( ans, f[p & 1][i][0] );
	printf ( "%lld\n", ans );
	return 0;
}

\(\mathcal{Details}\)

  面向資料程式設計 de 了好久 bug……DP 初值什麼的一定不能想當然吶。