首页 > 其他分享 >解题 [HNOI2008] GT考试

解题 [HNOI2008] GT考试

时间:2023-11-04 16:23:23浏览次数:29  
标签:tmp GT 匹配 matrix int times 解题 HNOI2008 21

题目:[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;
}

标签:tmp,GT,匹配,matrix,int,times,解题,HNOI2008,21
From: https://www.cnblogs.com/Cnghit/p/17809489.html

相关文章

  • Windows server 2022 搭建 AD 域服务器<01>
    1.AD(ActiveDirectory)WindowsServer环境准备AD应用程序:ActiveDirectory域控制器主机名称IP角色AD-Server192.168.61.237AD服务器2.配置AD环境地址3.添加角色和功能配置域控制器配置DSRM密码:Lahmy1c!安装后会自动重启服务器,重启后,系统将......
  • 列表/表格搜索方法->前端实现
    很多业务系统中都会用到表格/列表,大部分都是用组件,配合搜索接口可以实现,搜索按钮是发送请求获取数据来更新表格数据。但不是所有的列表都会有对应的后端搜索接口,比如在对一个弹窗里面的列表进行选择,数据量不是特别大的情况下希望前端支持筛选,可以更加方便快捷的对数据进行操作,这......
  • C# list<T>去重
     一、值类型去重 1、List<object>1.1、objectisint//objectisintList<object>ointList=newList<object>();ointList.Add(1);ointList.Add(1);ointList.Add(2);ointList=ointLi......
  • prometheus-webhook-dingtalk 报警模板
    moretemplate.tmpl{{define"__subject"}}[{{.Status|toUpper}}{{ifeq.Status"firing"}}:{{.Alerts.Firing|len}}{{end}}]{{end}}{{define"__alert_list"}}{{range.}}---**告警名称**:{{index.Annotations"ti......
  • <学习笔记> 点分树
    感觉可以理解为带修点分治。常用于解决与树原形态无关的带修改问题。——oi-wiki点分树是通过更改原树形态使树的层数变为稳定\(\logn\)的一种重构树。就是通过点分治找重心的方式,将这一层重心为上一层重心的儿子。所以对于很多暴力的复杂度是正确的。一开始发现建树错了......
  • 舵机驱动——STM32F407ZGT6探索者——HAL库
    舵机驱动——STM32F407ZGT6探索者——HAL库1、材料准备开发板:正点原子STM32F407ZGT6探索者舵机:SG90舵机线材分辨:褐色/红色/橘黄色——GND/VCC/PWM_signal与开发板接线:褐色/红色/橘黄色——GND/+5V/PF6(任选的PF6)2、知识准备2.1、舵......
  • 从嘉手札<2023-11-01>
    最近心态不好,如同此刻的天气,浓雾扰扰,看不见前途未来,也想不起过去。一则是研究没有进展,二则是感情纷扰,其实再多的纷扰也都不过是自己内心的那层桎梏,可人不能总能保持理性的;就像很多快乐的事情是简单的,其实并没有那么难以获得,晒晒阳光,逗逗小猫,打场篮球。可快乐是不能帮助你毕业的......
  • 解题报告 P2572 [SCOI2010] 序列操作
    P2572[SCOI2010]序列操作线段树。首先对于一个区间,我们需要存储\(8\)个量来保证算出答案:\(1\)的个数,\(0\)的个数,最左边连续\(1/0\)个数,最右边连续\(1/0\)个数,区间内最长连续\(1/0\)个数。可以如下定义一个节点:structnode{ intcnt1,cnt0,ls1,ls0,rs1,rs0,ss1,s......
  • 《CF1889C2 Doremy's Drying Plan (Hard Version)》 解题报告
    考场上不会做。如果考虑删掉哪些区间实际上不太可做。正难则反,转化贡献,考虑哪些点可以有贡献。显然一个点如果可能有贡献,那么当且仅当覆盖它的区间\(\leK\)个。于是我们记一个状态\(f_{i,j}\)表示前\(i\)个点中,\(i\)是最后一个贡献的点,已经删除了\(j\)段区间了。......
  • 开源GTKSystem.Windows.Forms,在这里更新预告
    开源GTKSystem.Windows.Forms,在这里更新预告gitee码云开源地址:https://gitee.com/easywebfactory/gtksystem-windows-formsgithub网络有墙,暂时就不上github了。目前利用空余时间持续开发更新,欢迎留言交流。更新预告:增加Timer类修改按钮的背景图属性生成方式实现控件的Pain......