4180: 字符串计数 后缀自动机+最小表示法+倍增floyd

本文介绍了一个字符串构造问题,目标是在给定长度N和特定字符集的情况下,寻找能够通过最少操作次数构造出的目标字符串,并分析了解决该问题的算法实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

4180: 字符串计数

Time Limit: 10 Sec   Memory Limit: 128 MB
Submit: 349   Solved: 152
[ Submit][ Status][ Discuss]

Description

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

Input

第一行包含一个整数N,表示要构造的字符串长度。
第二行包含一个字符串T,T的意义如题所述。

Output

输出文件包含一行,一个整数,为你所求出的最大的操作次数。


题解:

和其他不太样,但思路都是最小表示。考虑要最少次数构造所以尽量在后面切割。长度很大所以f[i][j]记录以i开头以j结尾的字符串恰好不是T的子串的最小长度。这样两个串f[i][j],f[j][k]就可以拼成f’[i][k](f'[i][k] = min(f[i][j] +f[j][k] - 1)。这东西可以用倍增floyd转移,求串长小于n的最大操作次数+1


注意:longlong 范围 9e18

            如果字符集不全要特判,虽然此题不会


还有,其实可以直接用floyd找最小环,不用倍增,但是此题的复杂度瓶颈不在此,所以懒得写了

#include<bits/stdc++.h>
using namespace std;
#define maxn 200020
#define inf 0x3f3f3f3f
#define INF 2e18 //longlong 范围到9e18

typedef long long ll;
ll len,f[4][4],sum[62][4][4],ans[4][4],tmp[4][4],num;
int n;
char ch[maxn];
struct SAM{
	int next[maxn][4],tot,val[maxn],pnt[maxn];
	int a[maxn],b[maxn],f[maxn][4],last;
	void Add(int x){
		int np = ++tot , p = last;
		val[np] = val[p] + 1;
		while ( p && !next[p][x] ) next[p][x] = np , p = pnt[p];
		int q = next[p][x];
		if ( !q ) next[p][x] = np , pnt[np] = p;
		else if ( val[p] + 1 == val[q] ) pnt[np] = q;
		else{
			int nq = ++tot;
			val[nq] = val[p] + 1;
			pnt[nq] = pnt[q];
			pnt[q] = pnt[np] = nq;
			memcpy(next[nq],next[q],sizeof(next[q]));
			while ( p && next[p][x] == q ) next[p][x] = nq , p = pnt[p];
			if ( next[p][x] == q ) next[p][x] = nq;
		}
		last = np;
	}	
	void sort(){
		for (int i = 1 ; i <= tot ; i++) a[val[i]]++;
		for (int i = 1 ; i <= n ; i++) a[i] += a[i - 1];
		for (int i = tot ; i >= 1 ; i--) b[a[val[i]]--] = i;
	}
	void pre(){
		for (int i = tot ; i >= 1 ; i--){
			int x = b[i];
			for (int j = 0 ; j < 4 ; j++){
				if ( !next[x][j] ) f[x][j] = 1;
				else f[x][j] = inf;
			}
			for (register int j = 0 ; j < 4 ; j++){
				for (register int k = 0 ; k < 4 ; k++){
					if ( next[x][k] )
						f[x][j] = min(f[x][j],f[next[x][k]][j] + 1);
				}
			}
		}
	}
	void print(){
		for (int i = 1 ; i <= tot ; i++) cout<<i<<" "<<pnt[i]<<endl;
		cout<<"f :"<<endl;
		for (int i = 1 ; i <= tot ; i++){
			cout<<i<<" : "<<endl;
			for (int j = 0 ; j < 4 ; j++)
				cout<<f[i][j]<<" ";
			cout<<endl;
		}
	}
}sam;

void init(){
	for (int i = 1 ; i <= n ; i++) sam.Add(ch[i] - 'A');
	memset(f,0x3f,sizeof(f));
	sam.sort() , sam.pre();
	//sam.print();
	
	for (int i = 0 ; i < 4 ; i++){
		for (int j = 0 ; j < 4 ; j++){
			if ( sam.next[0][i] && sam.next[0][j] ) f[i][j] = min(f[i][j],(ll)sam.f[sam.next[0][i]][j] + 1);
			else f[i][j] = INF; 
			//如果有字母未出现在T中则不能选,但题目保证都出现
		}
	}

/*	for (int i = 0 ; i < 4 ; i++){
	   	for (int j = 0 ; j < 4 ; j++) cout<<f[i][j]<<" ";
		cout<<endl;
	}*/
}
inline void Add(ll &x,ll y){
	x = min(x,y);
	if ( x > INF ) x = INF;
}
void power(ll a[][4],ll b[][4],ll c[][4]){
	for (int i = 0 ; i < 4 ; i++)
		for (int j = 0 ; j < 4 ; j++)
			for (int k = 0 ; k < 4 ; k++)
				Add(a[i][j],b[i][k] + c[k][j] - 1);
}	
bool check(int x){
	memset(tmp,0x3f,sizeof(tmp));
	power(tmp,ans,sum[x]);
	for (int i = 0 ; i < 4 ; i++)
		for (int j = 0 ; j < 4 ; j++)
			if ( tmp[i][j] < len ) return 1;
	return 0;
}
void solve(){
	memset(sum,0x3f,sizeof(sum));
	memcpy(sum[0],f,sizeof(f));
	//可以不用倍增,直接找最小环,然而不想写了
	for (int i = 1 ; i <= 60 ; i++){
		power(sum[i],sum[i - 1],sum[i - 1]);
	}
	for (int i = 60 ; i >= 0 ; i--){
		if ( check(i) ) memcpy(ans,tmp,sizeof(tmp)) , num += 1ll << i;
	}
}
int main(){
//	freopen("input.txt","r",stdin);
	scanf("%lld",&len) , scanf("%s",ch + 1) , n = strlen(ch + 1);
	init();
	solve();
	printf("%lld\n",num + 1); //再拼一个长度才是n
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值