ABC292G Count Strictly Increasing Sequences [区间DP]

Description

你有 \(n\) 个数,每个数长度为 \(m\)

不过这 \(n\) 个数中,可能有某些位不确定,需要你在每个 ? 位置上 \(0\)\(9\) 之间填一个数。设你填出来的序列是 \(\{S_i\}\)

请你求出,在所有可能的填数方案中,有多少种满足 \(S_1 < S_2 < \dots < S_n\)?对 \(998244353\) 取模。允许前导零存在。

\(n,m \le 40\)

Solution

推性质:

我们考虑一个合法的序列长什么样,它的每个数的最高位可能是这样的,111...222...333...444......999,

其中,最高位的数严格递增,即可保证序列严格递增,那如果最高位相等,就要求往后一位(去掉最高位后)严格递增。

设状态:

\(f(i,j,l,r)\) 表示,当前考虑了后 \(i\) 位(越靠前的位权越高)(\(i\)\(m\) 这一维的),\([l,r]\) (是 \(n\) 这一维的) 这个区间的数严格递增,且当前填的最大的数是 \(j\),的方案数。

举个例子:\(12345,12356,12378,12390\)\(f(2,1,4,9)\) 就表示 \([1,4]\) 的数,\(45<56<78<90\),且最大的数是 \(\max\{4,5,7,9\}\)

写转移:

我们枚举一个 \(k\),表示 \([l,k]\) 的数严格递增,\([k+1,r]\) 的数最高位相等,

转移即是:\(f(i,j,l,r)=\sum f(i,j-1,l,k)\times f(i+1,9,k+1,r)\)

含义就是,既然 \([l,k]\) 的数严格递增,于是我们就直接算上它的贡献,然后再保证 \([k+1,r]\) 严格递增,保证的方法就是让它上一位严格递增。

然后 \(j\) 这一维是可以用滚动数组滚掉的。

时间:\(O(n^3m\left|\sum\right|)\),空间:\(O(n^3)\)

Code

const int N = 40 + 5, mod = 998244353;

int n, m;
string s[N];
int f[N][N][N], g[N][N][N]; // 滚动数组

void Solve(){
	cin >> n >> m;
	for(int i = 1; i <= n; i++){
		cin >> s[i];
		s[i] = " " + s[i];
	}
	for(int i = 1; i <= n; i++) f[m + 1][i][i] = 1; // 边界
	for(int i = m; i >= 1; i--){
		for(int j = 0; j <= 9; j++){
			memcpy(g[i], f[i], sizeof(f[i]));
			for(int len = 1; len <= n; len++){
				for(int l = 1; l + len - 1 <= n; l++){
					int r = l + len - 1;
					bool flag = true;
					for(int k = r; k >= l; k--){
						if(s[k][i] == '?' || s[k][i] == j + '0'){
							if(k == l) break;
							f[i][l][r] = (1ll * f[i][l][r] + 1ll * g[i][l][k - 1] * f[i + 1][k][r] % mod) % mod;
						}else{
							flag = false;
							break;
						}
					} // 个人代码实现,最后需要特判整个区间都相等,不必纠结
					if(flag) f[i][l][r] = (1ll * f[i][l][r] + f[i + 1][l][r]) % mod;
				}
			}
		}
	}
	cout << f[1][1][n] << endl;
}
posted @ 2024-10-11 17:04  chenwenmo  阅读(2)  评论(0编辑  收藏  举报