【luogu SP1676】【SPOJ 1676】GEN - Text Generator

GEN - Text Generator

题目链接:luogu SP1676 / SPOJ 1676

题目大意

给定n(n<=10)个字符串(长度<=6),要求构造一个长度为L(<=10^6)的字符串,至少包含n个字符串中任何一个,求方案数mod10007的值(所有字符都是大写字母)

思路

这道题其实就是文本生成器的升级版,没有做过的可以先做一做。

这里就默认你们已经知道了文本生成器的做法。
那我们观察一下它 dp 转移的式子,你就会发现你好像可以用矩阵乘法来做。

没错,你就建出转移矩阵,然后乘 L L L 个转移矩阵就可以了。
转移矩阵就是这个地方可以加上就标 1 1 1,否则就是 0 0 0

但是这道题很毒瘤,这样搞会 TLE。
我们就要取模的时候在取模(就是小于零或者大于 10007 10007 10007),而且我们还可以缩小矩阵的大小。
一开始我们的矩阵就是 Trie 树上节点数量,那很明显,bool 值是真的点肯定都是 0 0 0,那我们就直接把这些点,重新编号,从而使得矩阵的大小变小。

代码

#include<queue>
#include<cstdio>
#include<cstring>
#define mo 10007

using namespace std;

struct Trie {
	int son[26], fail;
	bool cant;
}tree[66];
struct matrix {
	int n, m;
	int a[66][66];
}A, re, B;
int n, m, now, thi, size, KK, ans, newKK, dy[66];
queue <int> q;
char c[11];

matrix operator *(matrix x, matrix y) {
	re.n = x.n;
	re.m = y.m;
	for (int i = 1; i <= re.n; i++)
		for (int j = 1; j <= re.m; j++)
			re.a[i][j] = 0;
	
	for (int k = 1; k <= x.m; k++)
		for (int i = 1; i <= re.n; i++)
			for (int j = 1; j <= re.m; j++) {
				re.a[i][j] += x.a[i][k] * y.a[k][j];
				if (re.a[i][j] >= mo) re.a[i][j] %= mo;
			}
	
	return re;
}

void jzksm(int times) {
	B = A;
	times--;
	while (times) {
		if (times & 1) B = B * A;
		A = A * A;
		times >>= 1;
	}
}

void build() {
	now = 0;
	for (int i = 0; i < size; i++) {
		thi = c[i] - 'A';
		if (!tree[now].son[thi]) tree[now].son[thi] = ++KK;
		now = tree[now].son[thi];
	}
	
	tree[now].cant = 1;
}

void get_fail() {
	for (int i = 0; i < 26; i++)
		if (tree[0].son[i]) {
			tree[tree[0].son[i]].fail = 0;
			q.push(tree[0].son[i]);
		}
	
	while (!q.empty()) {
		now = q.front();
		q.pop();
		
		for (int i = 0; i < 26; i++) {
			if (tree[now].son[i]) {
				tree[tree[now].son[i]].fail = tree[tree[now].fail].son[i];
				q.push(tree[now].son[i]);
				tree[tree[now].son[i]].cant |= tree[tree[tree[now].son[i]].fail].cant;
			}
			else tree[now].son[i] = tree[tree[now].fail].son[i]; 
		}
	}
} 

int ksm(int x, int y) {
	int ree = 1;
	while (y) {
		if (y & 1) ree = (ree * x) % mo;
		x = (x * x) % mo;
		y >>= 1;
	}
	return ree;
}

int main() {
	while (scanf("%d %d", &n, &m) != EOF) {
		memset(A.a, 0, sizeof(A.a));
		memset(tree, 0, sizeof(tree));
		KK = 0;
		newKK = 0; 
		
		for (int i = 1; i <= n; i++) {
			scanf("%s", &c);
			size = strlen(c);
			build();
		}
		
		get_fail();
		
		for (int i = 0 ; i <= KK; i++)
			if (!tree[i].cant) dy[i] = ++newKK;//把能用的点找出来,以矩阵大小,减少时间
		for (int i = 0; i <= KK; i++) {//构建转移矩阵
			if (tree[i].cant) continue;
			
			for (int j = 0; j < 26; j++)
				if (!tree[tree[i].son[j]].cant)
					A.a[dy[i]][dy[tree[i].son[j]]]++;
		}
		A.n = newKK;
		A.m = newKK;
		
		jzksm(m);
		
		ans = ksm(26, m);
		for (int i = 1; i <= newKK; i++) {
			ans = ans - B.a[1][i];
			if (ans < 0) ans += mo;
		}
		
		printf("%d\n", ans);
	}
	
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值