BZOJ4180:字符串计数(后缀自动机+Floyd+倍增+二分)

这题是打错题号找到的
题面
题意:给出一个串,用它的子串前后拼接来生成串
问所以长为n的串最少拼接次数的最大值

拼接次数最少就要求每个子串都要是极长的
即它不能和后一个子串的任何前缀形成新的子串

由于子串的某种性质,所以只和首字母有关
即它不能和后一个子串的首字母形成新的子串
在后缀自动机表现为该状态没有对应儿子

由于要拼接次数最大,故每个状态要在关注首字母的情况下取最短
f[i][j][k] f [ i ] [ j ] [ k ] 表示状态i在首字母为j时能接k的最短长度

但这样很蠢,因为状态都是相同的,可以把i去掉

然后得到一个 44 4 ∗ 4 的矩阵
二分拼接次数mid
对该矩阵做Floyd的倍增
若存在一条长大于等于n的路径
则mid有可能是答案

Kscla有一种厉害的纯图论的想法
在SAM的正向DAG上
若某状态没有某个儿子
则设为初始状态的对应儿子,并附上价值1
就变成求一条长为n的价值最小的路径
把价值为0的边缩掉
就变成一个4个点的完全图

#include <iostream>
#include <fstream>
#include <algorithm>
#include <cmath>
#include <ctime>
#include <cstdio>
#include <cstdlib>
#include <cstring>

using namespace std;
#define mmst(a, b) memset(a, b, sizeof(a))
#define mmcp(a, b) memcpy(a, b, sizeof(b))

typedef long long LL;

const int N=200200;
const LL oo=1e18+520;

LL len;
int n;
int son[N][4],dep[N],pre[N],cnt=1,last=1;
LL dp[N][4];
bool vis[N];
char s[N];

struct yy
{
    LL f[4][4];
}e,tu,big;

yy operator *(yy x,yy y)
{
    yy hy=big;
    for(int i=0;i<4;i++)
    for(int j=0;j<4;j++)
    for(int k=0;k<4;k++)
    hy.f[i][j]=min(hy.f[i][j],x.f[i][k]+y.f[k][j]);
    return hy;
}

void Insert(int x)
{
    dep[++cnt]=dep[last]+1;
    int np=cnt,p=last;
    last=cnt;
    for(;!son[p][x];p=pre[p])
    son[p][x]=np;
    if(!p)
    pre[np]=1;
    else
    {
        int q=son[p][x];
        if(dep[q]==dep[p]+1)
        pre[np]=q;
        else
        {
            dep[++cnt]=dep[p]+1;
            int nq=cnt;
            pre[nq]=pre[q];
            pre[q]=pre[np]=nq;
            mmcp(son[nq],son[q]);
            for(;son[p][x]==q;p=pre[p])
            son[p][x]=nq;
        }
    }
}

void dfs(int x)
{
    if(vis[x])
    return;
    vis[x]=1;
    for(int i=0;i<4;i++)
    if(son[x][i])
    dfs(son[x][i]),dp[x][i]=oo;

    for(int i=0;i<4;i++)
    {
        if(!son[x][i])
        dp[x][i]=1;
        else
        for(int j=0;j<4;j++)
        dp[x][j]=min(dp[x][j],dp[son[x][i]][j]+1);
    }
}

bool ok(LL b)
{
    yy re=e,a=tu;
    LL res=oo;
    for(;b;b>>=1,a=a*a)
    if(b&1)
    re=re*a;

    for(int i=0;i<4;i++)
    for(int j=0;j<4;j++)
    res=min(res,re.f[i][j]);
    return res>=len;
}

int main()
{
    for(int i=0;i<4;i++)
    for(int j=0;j<4;j++)
    if(i!=j)
    e.f[i][j]=oo;
    for(int i=0;i<4;i++)
    for(int j=0;j<4;j++)
    big.f[i][j]=oo;

    cin>>len;
    scanf("%s",s+1);
    n=strlen(s+1);
    for(int i=1;i<=n;i++)
    Insert(s[i]-'A');

    dfs(1);
    for(int i=0;i<4;i++)
    for(int j=0;j<4;j++)
    tu.f[i][j]=dp[son[1][i]][j];

    LL l=1,r=len+1,ans=len+1;
    while(l<=r)
    {
        LL mid=(l+r)>>1;
        if(ok(mid))
        ans=min(ans,mid),r=mid-1;
        else
        l=mid+1;
    }
    if(ans==len+1)
    cout<<0<<endl;
    else
    cout<<ans<<endl;

    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值