BZOJ4180: 字符串计数 SAM+矩阵乘法

Description
SD有一名神犇叫做Oxer,他觉得字符串的题目都太水了,于是便出了一道题来虐蒟蒻yts1999。
他给出了一个字符串T,字符串T中有且仅有4种字符 ‘A’, ‘B’, ‘C’, ‘D’。现在他要求蒟蒻yts1999构造一个新的字符串S,构造的方法是:进行多次操作,每一次操作选择T的一个子串,将其加入S的末尾。
对于一个可构造出的字符串S,可能有多种构造方案,Oxer定义构造字符串S所需的操作次数为所有构造方案中操作次数的最小值。
Oxer想知道对于给定的正整数N和字符串T,他所能构造出的所有长度为N的字符串S中,构造所需的操作次数最大的字符串的操作次数。
蒟蒻yts1999当然不会做了,于是向你求助。


Sample Input
5
ABCCAD


Sample Output
5


首先这题可以确定是矩阵乘法。
考虑构建矩阵,设 f [ i ] [ j ] f[i][j] f[i][j] i i i字符最少往后随便加 f [ i ] [ j ] − 1 f[i][j]-1 f[i][j]1个字符后再加一个字符 j j j可以是这个字符串不在原串中。
如果构造出这个矩阵可以考虑二分+矩阵乘法验证。
那么就是如何构造出这个矩阵的问题。
考虑设 h h [ i ] [ j ] hh[i][j] hh[i][j]为SAM编号为i的点最少往后随便加 h h [ i ] [ j ] − 1 hh[i][j]-1 hh[i][j]1个字符后再加一个字符 j j j可以是这个字符串不在原串中。
那么 h h [ i ] [ j ] = m i n ( h h [ i ] [ j ] , h h [ t [ i ] . s o n [ c ] ] [ j ] ) hh[i][j]=min(hh[i][j],hh[t[i].son[c]][j]) hh[i][j]=min(hh[i][j],hh[t[i].son[c]][j])
f [ i ] [ j ] = h h [ t [ 1 ] . s o n [ i ] ] [ j ] f[i][j]=hh[t[1].son[i]][j] f[i][j]=hh[t[1].son[i]][j]


#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;
typedef long long LL;
LL _min(LL x, LL y) {return x < y ? x : y;}
LL _max(LL x, LL y) {return x > y ? x : y;}
int read() {
	int s = 0, f = 1; char ch = getchar();
	while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
	while(ch >= '0' && ch <= '9') s = s * 10 + ch - '0', ch = getchar();
	return s * f;
}

struct node {
	LL a[4][4];
	node() {memset(a, -1, sizeof(a));}
	friend node operator * (node a, node b) {
		node c;
		for(int i = 0; i < 4; i++)
			for(int j = 0; j < 4; j++)
				for(int k = 0; k < 4; k++)
					c.a[i][j] = (c.a[i][j] == -1 ? a.a[i][k] + b.a[k][j] : _min(c.a[i][j], a.a[i][k] + b.a[k][j]));
		return c;
	}
} ST, ONE;
struct SAM {
	int son[4], len, fa;
} t[1000010]; int root, last, cnt;
int hh[1000010][4];
char ss[1000010];
bool v[1000010];
int n; LL len;

void insam(int c) {
	int np = ++cnt, p = last;
	t[np].len = t[p].len + 1;
	while(p && !t[p].son[c]) t[p].son[c] = np, p = t[p].fa;
	if(!p) t[np].fa = root;
	else {
		int q = t[p].son[c];
		if(t[p].len + 1 == t[q].len) t[np].fa = q;
		else {
			int nq = ++cnt;
			t[nq] = t[q];
			t[nq].len = t[p].len + 1;
			t[q].fa = t[np].fa = nq;
			while(p && t[p].son[c] == q) t[p].son[c] = nq, p = t[p].fa;
		}
	} last = np;
}

void bt_SAM() {
	cnt = last = root = 1;
	for(int i = 1; i <= n; i++) insam(ss[i] - 'A');
}

void dfs(int x) {
	if(v[x]) return ;
	v[x] = 1;
	for(int i = 0; i < 4; i++) if(t[x].son[i]){
		dfs(t[x].son[i]), hh[x][i] = 999999999;
	} for(int i = 0; i < 4; i++) {
		if(!t[x].son[i]) {hh[x][i] = 1; continue;}
		for(int j = 0; j < 4; j++) hh[x][j] = _min(hh[x][j], hh[t[x].son[i]][j] + 1);
	}
}

void get() {
	dfs(root);
	for(int i = 0; i < 4; i++)
		for(int j = 0; j < 4; j++)
			if(t[1].son[i]) ST.a[i][j] = hh[t[1].son[i]][j];
			else ST.a[i][j] = 999999999;
}

bool check(LL mid) {
	node ans = ONE, A = ST;
	while(mid) {
		if(mid & 1LL) ans = ans * A;
		A = A * A; mid /= 2LL;
	} LL mn = ans.a[0][0];
	for(int i = 0; i < 4; i++)
		for(int j = 0; j < 4; j++)
			mn = _min(mn, ans.a[i][j]);
	return mn >= len;
}

int main() {
	scanf("%lld", &len);
	scanf("%s", ss + 1);
	n = strlen(ss + 1);
	bt_SAM();
	get();
	LL l = 1, r = len, ans = len + 1;
	for(int i = 0; i < 4; i++) for(int j = 0; j < 4; j++) ONE.a[i][j] = 0;
	for(int i = 0; i < 4; i++) ONE.a[i][i] = 1;
	while(l <= r) {
		LL mid = (l + r) / 2;
		if(check(mid)) r = mid - 1, ans = mid;
		else l = mid + 1;
	} printf("%lld\n", ans);
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值