字符串构造的dp 【bzoj1009 &bzoj1030】

1009: [HNOI2008]GT考试

Time Limit: 1 Sec   Memory Limit: 162 MB
Submit: 4305   Solved: 2637
[ Submit][ Status][ Discuss]

Description

  阿申准备报名参加GT考试,准考证号为N位数X1X2....Xn(0<=Xi<=9),他不希望准考证号上出现不吉利的数字。
他的不吉利数学A1A2...Am(0<=Ai<=9)有M位,不出现是指X1X2...Xn中没有恰好一段等于A1A2...Am. A1和X1可以为
0

Input

  第一行输入N,M,K.接下来一行输入M位的数。 N<=10^9,M<=20,K<=1000

Output

  阿申想知道不出现不吉利数字的号码有多少种,输出模K取余的结果.

Sample Input

4 3 100
111

Sample Output

81


设f[i][j]表示到第i个位置后缀为不吉利数字[1....j]的方案数

我们令a[i][j]表示不吉利数字由i号位转j号位的方案数

显然f[i][j] = f[i - 1][0] * a[0][j] + f[i - 1][1] * a[1][j] + f[i - 1][2] * a[2][j]......

但N很大我们不可能直接推

观察出这是f的一个齐次式,所以转矩阵快速幂

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define LL long long int
#define REP(i,n) for (int i = 1; i <= (n); i++)
#define fo(i,x,y) for (int i = (x); i <= (y); i++)
#define Redge(u) for (int k = head[u]; k != -1; k = edge[k].next)
using namespace std;
const int maxn = 100005,maxm = 25,INF = 1000000000;

int n,m,P;
char s[maxn];
int nxt[maxn];

struct Matrix{
	int s[maxm][maxm],n,m;
	Matrix() {memset(s,0,sizeof(s)); n = m = 0;}
}A;

inline Matrix operator * (const Matrix& a,const Matrix& b){
	Matrix c;
	if (a.m != b.n) return c;
	c.n = a.n; c.m = b.m;
	for (int i = 0 ; i < c.n; i++)
		for (int j = 0; j < c.m; j++)
			for (int k = 0; k < a.m; k++)
				c.s[i][j] = (c.s[i][j] + a.s[i][k] * b.s[k][j]) % P;
	return c;
}

inline Matrix qpow(Matrix a,LL b){
	Matrix ans; ans.n = ans.m = a.n;
	for (int i = 0; i < ans.n; i++) ans.s[i][i] = 1;
	for (; b; b >>= 1,a = a * a)
		if (b & 1) ans = ans * a;
	return ans;
}

void getf(){
	for (int i = 1 ; i < m; i++){
		int j = nxt[i];
		while (j && s[j] != s[i]) j = nxt[j];
		nxt[i + 1] = s[j] == s[i] ? j + 1 : 0;
	}
}

void init(){
	cin>>n>>m>>P>>s;
	getf();
	for (int i = 0; i < m; i++){
		for (char j = '0'; j <= '9'; j++){
			int k = i;
			while (k && s[k] != j) k = nxt[k];
			if (s[k] == j) k++;
			if (k != m) A.s[i][k]++;
		}
	}
	A.n = A.m = m;
}

void solve(){
	Matrix F = qpow(A,n),Ans;
	Ans.n = 1; Ans.m = m;
	Ans.s[0][0] = 1;
	Ans = Ans * F;
	int ans = 0;
	for (int i = 0; i < m; i++)
		ans = (ans + Ans.s[0][i]) % P;
	cout<<ans<<endl;
}

int main()
{
	init();
	//cout<<"h"<<endl;
	solve();
	return 0;
}


1030: [JSOI2007]文本生成器

Time Limit: 1 Sec   Memory Limit: 162 MB
Submit: 5248   Solved: 2166
[ Submit][ Status][ Discuss]

Description

  JSOI交给队员ZYX一个任务,编制一个称之为“文本生成器”的电脑软件:该软件的使用者是一些低幼人群,
他们现在使用的是GW文本生成器v6版。该软件可以随机生成一些文章―――总是生成一篇长度固定且完全随机的文
章—— 也就是说,生成的文章中每个字节都是完全随机的。如果一篇文章中至少包含使用者们了解的一个单词,
那么我们说这篇文章是可读的(我们称文章a包含单词b,当且仅当单词b是文章a的子串)。但是,即使按照这样的
标准,使用者现在使用的GW文本生成器v6版所生成的文章也是几乎完全不可读的?。ZYX需要指出GW文本生成器 v6
生成的所有文本中可读文本的数量,以便能够成功获得v7更新版。你能帮助他吗?

Input

  输入文件的第一行包含两个正整数,分别是使用者了解的单词总数N (<= 60),GW文本生成器 v6生成的文本固
定长度M;以下N行,每一行包含一个使用者了解的单词。这里所有单词及文本的长度不会超过100,并且只可能包
含英文大写字母A..Z

Output

  一个整数,表示可能的文章总数。只需要知道结果模10007的值。

Sample Input

2 2
A
B

Sample Output

100


一眼看上去非常的像,但变成了多个字符串的匹配,就改为AC自动机

f[i][j]表示到i位置后缀为trie树中的j节点的方案数

显然f[i][j]可以由f[i - 1][k]转来,只要存在ch[k][...] = j的指针

我们就正向dp,推出f,要注意的是有tag标记成功匹配的地方是不能算的,且若fail指针指向一个tag标记的节点,那么这个节点也要打tag标记,fail指向的字符串一定是当前的后缀,后缀存在合法字符串,当然不行了


#include<iostream>
#include<cstdio>
#include<queue>
#include<cstring>
#include<algorithm>
#define LL long long int
#define REP(i,n) for (int i = 1; i <= (n); i++)
#define fo(i,x,y) for (int i = (x); i <= (y); i++)
#define Redge(u) for (int k = head[u]; k != -1; k = edge[k].next)
using namespace std;
const int maxn = 10005,maxm = 105,INF = 1000000000,P = 10007;

int N,M,ch[maxn][26],tag[maxn],fail[maxn],siz = 0,f[maxm][maxn];
char A[maxm];
void insert(){
	int n = strlen(A),u = 0,id;
	for (int i = 0; i < n; i++){
		id = A[i] - 'A';
		u = ch[u][id] ? ch[u][id] : ch[u][id] = ++siz;
	}
	tag[u] = true;
}
void getf(){
	queue<int> q;
	for (int i = 0; i < 26; i++) if (ch[0][i]) q.push(ch[0][i]);
	int u,v;
	while (!q.empty()){
		u = q.front();
		q.pop();
		for (int i = 0; i < 26; i++){
			v = ch[u][i];
			if (!v) ch[u][i] = ch[fail[u]][i];
			else fail[v] = ch[fail[u]][i],q.push(v);
		}
		if (tag[fail[u]]) tag[u] = true;
	}
}
int main()
{
	cin >> N >> M;
	REP(i,N) scanf("%s",A),insert();
	getf(); f[0][0] = 1;
	for (int i = 0; i < M; i++){
		for (int j = 0; j <= siz; j++){
			if (tag[j] || !f[i][j]) continue;
			for (int k = 0; k < 26; k++){
				int v = ch[j][k];
				f[i + 1][v] = (f[i + 1][v] + f[i][j]) % P;
			}
		}
	}
	int ans = 1;
	REP(i,M) ans = ans * 26 % P;
	for (int i = 0; i <= siz; i++) if (!tag[i]) ans = (ans - f[M][i] + P) % P;
	cout<<ans<<endl;
	return 0;
}


转载于:https://www.cnblogs.com/Mychael/p/8282819.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值