解题 [HNOI2008] GT考试

发布时间 2023-11-04 16:22:08作者: Ciaxin

题目:[HNOI2008] GT考试

阿申准备报名参加 GT 考试,准考证号为 \(N\) 位数\(X_1,X_2…X_n\ (0\le X_i\le 9)\),他不希望准考证号上出现不吉利的数字。
他的不吉利数字\(A_1,A_2,\cdots, A_m\ (0\le A_i\le 9)\)\(M\) 位,不出现是指 \(X_1,X_2\cdots X_n\) 中没有恰好一段等于 \(A_1,A_2,\cdots ,A_m\)\(A_1\)\(X_1\) 可以为 \(0\)
阿申想知道不出现不吉利数字的号码有多少种,输出模 \(K\) 取余的结果。

对于全部数据,\(N\leq10^9\)\(M\leq 20\)\(K\leq1000\)


这个题的做法是这样的:

这个题涉及到字符串匹配,题目中的“不出现”,也就代表着不吉利数字无法匹配到 \(m\)

1、最暴力的想法是将准考证号枚举出来,然后在进行字符串匹配,这样的复杂度是 \(\mathcal O(10^N\times N)\) 的。

2、当使用 KMP 做字符串匹配的时候,我们可以考虑这样一个过程:

  • \(X\) 匹配到第 \(i\) 位,\(A\) 匹配到第 \(k\) 位,在下一个位置发生了失配。
  • 然后跳 \(\text{border}\) 到下一个状态,\(X\) 匹配到第 \(i+1\) 位,\(A\) 匹配到第 \(j\) 位。

也就是由 \(q_i(k)+c\to q_{i+1}(j)\) 的转移。考虑到只需要求方案数,我们设 \(f_i(j)\) 为当 \(X\) 匹配到第 \(i\) 位,\(A\) 匹配到第 \(j\) 位的合法的号码的方案数(当然 \(j\) 永远不会等于 \(m\))。

但是由 \(q_i(k)+c\to q_{i+1}(j)\) 的转移,会存在 \(0\) 或多种。那么考虑枚举字符 \(c\),来得到“由位置 \(k\) 的下一个位置失配到位置 \(j\) 的方案数”,记为 \(g(k,j)\)

for (int k = 0, j; k < m; k ++ )
	for (char c = '0'; c <= '9'; c ++ ) {
		for (j = nxt[k]; j && a[j + 1] != c; j = nxt[j]);
		g[k][j + (a[j + 1] == c)] ++;
	}

这样的话,就可以得到状态 \(f\) 的转移 \(f_i(j)\times g(j,k)\to f_{i+1}(k)\)

那么根据 \(f\) 的定义来看,就可以达到最终答案 \(\sum_{i=0}^{m-1}f_n(i)\),总时间复杂度为 \(\mathcal O(nm^2)\)

参考代码(\(40\text{ pts}\)):

#include <bits/stdc++.h>

using namespace std;

int n, m, K, ans, nxt[21], g[21][21], dp[10010][21];
char a[21];

int main() {
	cin >> n >> m >> K >> (a + 1);
	
	for (int i = 2, j = 0; i <= m; i ++ ) {
		for (j = nxt[i - 1];j && a[j + 1] != a[i]; j = nxt[j]);
		nxt[i] = a[j + 1] == a[i] ? j + 1 : 0;
	}
	
	for (int i = 0, j; i < m; i ++ ) {
		for (char c = '0'; c <= '9'; c ++ ) {
			for (j = i; j && a[j + 1] != c; j = nxt[j]);
			g[i][j + (a[j + 1] == c)] ++;
		}
	}
	
	dp[0][0] = 1;
	for (int i = 1; i <= n; i ++ ) {
		for (int j = 0; j < m; j ++ ) {
			for (int k = 0; k < m; k ++ ) {
				dp[i][j] = (dp[i - 1][k] * g[k][j] + dp[i][j]) %K;
			}
		}
	}
	
	for (int i = 0; i < m; i ++ ) ans = (ans + dp[n][i]) %K;
	cout << ans << '\n';
	return 0;
}

3、优化转移

分析状态 \(f\) 的转移 \(\sum_{i=0}^{m-1}f_i(j)\times g(j,k)\to f_{i+1}(k)\),从中可以得到 \(f_i(1,j)\times g(j,k)\to f_{i+1}(1,k)\),即一个矩乘的式子 \(f_i\times g=f_{i+1}\)

然后对转移进行矩阵加速幂,时间复杂度为 \(\mathcal O(m^3\log_2n)\) 的。

参考代码(\(100\text{ pts}\)):

#include <bits/stdc++.h>
using namespace std;


const int M = 31;

int n, m, K, res, nxt[M];

char s[M];

struct matrix {
    int a[M][M], n, m;
    matrix() {
        n = m = 0;
        memset(a, 0, sizeof(a));
    }
} F, G;

matrix operator* (const matrix &a, const matrix &b) {
    matrix tmp;
    tmp.n = a.n, tmp.m = b.m;
    for (int i = 0; i < tmp.n; i ++ ) {
        for (int j = 0; j < tmp.m; j ++ ) {
            for(int k = 0; k < a.m; k ++ ) {
                tmp.a[i][j] = (tmp.a[i][j] + a.a[i][k] * b.a[k][j] %K) %K;
            }
        }
    }
    return tmp;
}

matrix Power(matrix a, int k) {
    matrix ans = a; 
    for (k --; k; a = a * a, k >>= 1)
        if (k & 1) ans = ans * a;
    return ans;
}

int main()
{
    cin >> n >> m >> K >> (a + 1);
    
    F.n = 1, F.m = G.m = G.n = m;

    for (int i = 2, j = 0; i <= m; i ++ ) {
		for (j = nxt[i - 1];j && a[j + 1] != a[i]; j = nxt[j]);
		nxt[i] = a[j + 1] == a[i] ? j + 1 : 0;
	}
	
	for (int i = 0, j; i < m; i ++ ) {
		for (char c = '0'; c <= '9'; c ++ ) {
			for (j = i; j && a[j + 1] != c; j = nxt[j]);
			g[i][j + (a[j + 1] == c)] ++;
		}
	}

    F.a[0][0] = 1;

    F = F * Power(G, n);

    for (int i = 0; i < m; i ++ ) res = (res + F.a[0][i]) %mod; 

    cout << res << '\n';
    return 0;
}