[字符串与矩阵乘法]

[BZOJ 1009][HNOI 2008]GT考试

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

KMP+矩阵乘法

构造出转移矩阵,注意边界

如果长度为n矩阵乘n次再乘一个初始矩阵。

最后状态是匹配到各个位置上的方案数累加,注意m以后的值是没有转移的。

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

int n, m, md;

char str[maxn];

int nxt[maxn];

struct Matrix{
	int a[21][21];
	void clear(){memset(a, 0, sizeof a);}
	void set(){
		clear();
		for(int i = 0; i < m; i ++)
		    a[i][i] = 1;
	}
}mat, ans;

Matrix operator * (const Matrix& a, const Matrix& b){
	Matrix c; c.clear();
	for(int i = 0; i < m; i ++)
		for(int j = 0; j < m; j ++)
			for(int k = 0; k < m; k ++)
			    c.a[i][j] = (c.a[i][j] + a.a[i][k] * b.a[k][j]) % md;
	return c;
}

Matrix power(Matrix a, int b){
	Matrix ret; ret.set();
	while(b > 0){
		if(b & 1)ret = ret * a;
		b >>= 1;
		a = a * a;
	}return ret;
}

int main(){
	scanf("%d%d%d", &n, &m, &md);
	scanf("%s", str+1);
	nxt[0] = nxt[1] = 0;
	for(int i = 2; i <= m; i ++){
		int j = nxt[i-1];
		while(j && str[j+1] != str[i])
		    j = nxt[j];
		nxt[i] = str[j+1] == str[i] ? j+1 : 0;
	}
	
	for(int i = 0; i < m; i ++){
		for(int j = 0; j < 10; j ++){
			int k = i;
			while(k && (j^48) != str[k+1])
			    k = nxt[k];
			if(str[k+1] == (j^48))mat.a[i][k+1] ++;
			else mat.a[i][0] ++;
		}
	}
	ans.clear();
	ans.a[0][0] = 1;
	ans = ans * power(mat, n);
	int ret = 0;
	for(int i = 0; i < m; i ++)
		ret = (ret + ans.a[0][i]) % md;
	printf("%d
", ret);
	return 0;
}

[BZOJ 1030][JSOI 2007]文本生成器

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

AC自动机上的DP(貌似和矩阵乘法没什么关系??后面就有啦)

注意状态表示dp[i][j]表示长度为i的字符串匹配到j这个节点的方案数,只有没有标记的后继转移

#include <bits/stdc++.h>
#define maxn 5010
using namespace std;
typedef long long ll;
int n, m;
 
const int md = 10007;
 
ll power_mod(ll a, ll b, ll md){
    ll ret = 1;
    while(b > 0){
        if(b & 1)
            ret = ret * a % md;
        b >>= 1;
        a = a * a % md;
    }return ret;
}
 
int t[maxn][26], root = 0, size;
 
void init(){
    memset(t, -1, sizeof t);
    root = size = 0;
}
 
bool End[maxn];
 
char str[maxn];
 
void Insert(){
    int now = root, len = strlen(str+1);
    for(int i = 1; i <= len; i ++){
        int p = str[i] - 'A';
        if(t[now][p] == -1)
            t[now][p] = ++ size;
        now = t[now][p];
    }End[now] = true;
}
 
queue<int> Q;
 
int fail[maxn];
void Build_Fail(){
    for(int i = 0; i < 26; i ++)
        if(t[root][i] == -1)
            t[root][i] = root;
        else fail[t[root][i]] = root, Q.push(t[root][i]);
    while(!Q.empty()){
        int u = Q.front(); Q.pop();
        End[u] |= End[fail[u]];
        for(int i = 0; i < 26; i ++){
            if(t[u][i] == -1)
                t[u][i] = t[fail[u]][i];
            else fail[t[u][i]] = t[fail[u]][i], Q.push(t[u][i]);
        }
    }
}
 
int dp[110][maxn], vis[110][maxn];
 
int DP(int m, int u){
    if(m == 0)return 1;
    if(vis[m][u])return dp[m][u];
    vis[m][u] = true;
    for(int i = 0; i < 26; i ++)
        if(!End[t[u][i]])
            dp[m][u] = (dp[m][u] + DP(m-1, t[u][i])) % md;
    return dp[m][u];
}
 
int main(){
    init();
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; i ++){
        scanf("%s", str+1);
        Insert();
    }
    Build_Fail();
    printf("%d
", (int)((power_mod(26, m, md) - DP(m, root) + md) % md));
    return 0;
}

 [BZOJ 2553]禁忌

一个随机串,如何尽可能多的划分,划分出最多的禁忌串。

考虑一个序列,如何选取线段,使得两两交集为空?练习:3410: [Usaco2009 Dec]Selfish Grazing 自私的食草者

在AC自动机上跑,遇到一个节点贪心。

因为len很大所以考虑矩阵乘法。

新建一个点表示期望。转移的话如果遇到禁忌串转移回根

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

int n, len, a, size;

struct Matrix{
	long double a[maxn][maxn];
	void clear(){memset(a, 0, sizeof a);}
	void set(){clear();for(int i = 0; i <= size; i ++)a[i][i] = 1;}
}mat;

Matrix operator*(const Matrix& a, const Matrix& b){
	Matrix c; c.clear();
	for(int i = 0; i <= size; i ++)
		for(int j = 0; j <= size; j ++)
			for(int k = 0; k <= size; k ++)
			    c.a[i][j] += a.a[i][k] * b.a[k][j];
	return c;
}

Matrix power(Matrix a, int b){
	Matrix ret; ret.set();
	while(b > 0){
		if(b & 1)ret = ret * a;
		a = a * a;
		b >>= 1;
	}return ret;
}

char str[maxn];
int fail[maxn], t[maxn][26], root;
bool End[maxn];

void Insert(){
	int now = root, p, len = strlen(str + 1);
	for(int i = 1; i <= len; i ++){
		p = str[i] - 'a';
		if(t[now][p] == -1) t[now][p] = ++ size;
		now = t[now][p];
	}End[now] = true;
}

queue<int> Q;

void buildfail(){
	for(int i = 0; i < a; i ++){
		if(t[root][i] == -1)
		    t[root][i] = root;
		else Q.push(t[root][i]), fail[t[root][i]] = root;
	}
	while(!Q.empty()){
		int u = Q.front(); Q.pop();
		End[u] |= End[fail[u]];
		for(int i = 0; i < a; i ++){
			if(t[u][i] == -1)t[u][i] = t[fail[u]][i];
			else fail[t[u][i]] = t[fail[u]][i], Q.push(t[u][i]);
		}
	}
}

bool vis[maxn];

void buildmatrix(){
	Q.push(root); vis[root] = true;
	long double tmp = (long double)1 / a;
	while(!Q.empty()){
		int u = Q.front(); Q.pop();
		for(int i = 0; i < a; i ++){
			int v = t[u][i];
			if(End[v]){
				mat.a[u][root] += tmp;
				mat.a[u][size+1] += tmp;
			}
			else{
				mat.a[u][v] += tmp;
				if(vis[v])continue;
				Q.push(v);
				vis[v] = true;
			}
		}
	}
	size ++;
	mat.a[size][size] = 1;
}

int main(){
	memset(t, -1, sizeof t);
	scanf("%d%d%d", &n, &len, &a);
	for(int i = 1; i <= n; i ++)
	    scanf("%s", str + 1), Insert();
	buildfail();
	buildmatrix();
	Matrix ans = power(mat, len);
	printf("%.15lf
", (double)ans.a[0][size]);
	return 0;
}

  

 [monkey]

也不算矩阵乘法啦,就是个高斯消元。

我们已知f[n]期望为0,因为期望要逆着推,所以最后状态在f[0]中

#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#define maxn 1010
using namespace std;

char s[maxn];
int str[maxn];

typedef long long ll;

ll a[maxn][maxn];

const int md = 1e9 + 7;

int n, nxt[maxn];

ll power_mod(ll a, ll b = md - 2){
	ll ret = 1;
	while(b > 0){
		if(b & 1)ret = ret * a % md;
		a = a * a % md;
		b >>= 1;
	}return ret;
}

void Gauss(int n){
	for(int i = 0; i <= n; i ++){
		for(int j = i; j <= n; j ++){
			if(a[j][i]){
				for(int k = 0; k <= n + 1; k ++)
				    swap(a[i][k], a[j][k]);
				ll inv = power_mod(a[i][i]);
				for(int k = 0; k <= n + 1; k ++)
				    a[i][k] = a[i][k] * inv % md;
				break;
			}
		}
		if(a[i][i] == 0)continue;
		for(int j = 0; j <= n; j ++){
			if(i == j || a[j][i] == 0)continue;
			ll nw = a[j][i];
			for(int k = i; k <= n + 1; k ++){
				a[j][k] -= nw * a[i][k];
				a[j][k] %= md;
			}
		}
	}
}

int main(){
    freopen("monkey.in", "r", stdin);
	freopen("monkey.out", "w", stdout);
	scanf("%s", s + 1);
	n = strlen(s + 1);
	for(int i = 1; i <= n; i ++)
	    str[i] = s[i] - 48;
	for(int i = 2; i <= n; i ++){
		int j = nxt[i - 1];
		while(j && str[j + 1] != str[i])
		    j = nxt[j];
		nxt[i] = str[j + 1] == str[i] ? j + 1 : 0;
	}

	ll inv2 = power_mod(2);
	for(int i = 0; i < n; i ++){
        a[i][i] = a[i][n + 1] = 1;
		for(int nw = 0; nw < 2; nw ++){
			int j = i;
			while(j && str[j + 1] != nw)j = nxt[j];
            if(str[j + 1] == nw)j ++;
			a[i][j] += (md - inv2);
			a[i][j] %= md;
		}
	}
	a[n][n] = 1;
	
	Gauss(n);
	a[0][n + 1] = (a[0][n + 1] + md) % md;
	printf("%lld
", a[0][n + 1]);
	fclose(stdin);
	fclose(stdout);
	return 0;
}

  

给时光以生命,而不是给生命以时光。
原文地址:https://www.cnblogs.com/Candyouth/p/5370068.html