4180: 字符串计数
Time Limit: 10 Sec Memory Limit: 128 MBSubmit: 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;
}