BJ模拟 Bobo Number【KMP】

题目描述:

一个正整数叫作 bobo number 当且仅当它在十进制下可以表示成两个相同的整数(无前导0)拼接而成的结果。例如:12341234和3232都是 bobo number ,但1234321和123401234不是。

一个正整数叫作 almost bobo number 当且仅当这个整数在合并连续相同的数位后,其结果为 bobo number 。例如:1112223112233合并连续相同的数位后变成123123,所以它是 almost bobo number 。

Bobo 有一个非常大的正整数 n ,他想知道,严格小于 n 的最大的 almost bobo number 是多少。

n的位数不超过5000000.

解题思路:

本题解法:KMP(难)

显然,如果 n≤1010 ,无解。否则可以找到一组解。

如果 s 有 d(d>4) 位,那么 999…999898 ,即 (d−4) 个9后面加898,共 (d−1) 位也是bobo。如果不存在跟 s 同样长度的almost bobo number,那么上面的这组解即为最优解。现在,我们可以假设存在跟 s 同样长度的almost bobo number 。

显然,最优解一定跟 s 有最长的前缀,我们把这个前缀定义为 p 。为了找到 p ,我们枚举所有的前缀,然后判断每个前缀是否可能有解。

假设前缀为 s′ ,我们需要加一个数字 c(c < < <script type="math/tex" id="MathJax-Element-206"><</script>s[|s′|+1]) 。我们把 s′c 定义为字符串 t ,并将 t 合并相同数位。为了添加其它数字使得 t 变成bobo number ,我们需要找一个 t 的一个border u(即 t=uxu ,x为非空字符串),那么 t 后面只需要加 x 就可以变成 bobo 了。我们只需要保证 |t|−2|u|≤|s|−|s′|−1 即可保证有解(有些情况可能需要特判)。所以,我们需要找到字符串 t 的最长的长度小等于|t|/2的border 。用KMP可以O(1)求出。

找到最长前缀之后,我们只需要枚举每一位,从9到0,找到一个最大的答案(还是可以用上述的判断方法检查选中的前缀是否有解)。

注意本题要动态维护fail链,如果每次暴力跳就会是 O(n2) O ( n 2 ) 的,可以参考代码中的方法做到 O(n) O ( n ) ,还有 O(1) O ( 1 ) 求合法border的方法也可以参考代码。

#include<bits/stdc++.h>
#define ll long long
using namespace std;
int getint()
{
    int i=0,f=1;char c;
    for(c=getchar();(c!='-')&&(c<'0'||c>'9');c=getchar());
    if(c=='-')c=getchar(),f=-1;
    for(;c>='0'&&c<='9';c=getchar())i=(i<<3)+(i<<1)+c-'0';
    return i*f;
}
const int N=5000005;
int n,ans[N],t[N],nxt[N],fail[N][10];
char s[N];
void ext(int c,int len)
{
    nxt[len]=fail[len-1][c];t[len]=c;
    fail[len-1][c]=len;
    memcpy(fail[len],fail[nxt[len]],sizeof(fail[len]));
}
int find(int i,int len)
{
    if(nxt[i]*2<=len)return nxt[i];
    int d=i-nxt[i],c=(i-len/2-1)/d;
    return nxt[i-c*d];
}
bool check(int c,int i,int len)
{
    if(t[len-1]==c)
    {
        if(len==2)return n-i>=3;
        int b=find(len-1,len-1);
        return len-1-2*b<=n-i;
    }
    int tmp=fail[len-1][c];
    ext(c,len);int b=find(len,len);
    fail[len-1][c]=tmp;
    return len-2*b<=n-i;
}
bool solve()
{
    n=strlen(s+1);t[0]=10;
    if(n<4)return false;
    int i,j,mx=0;
    memset(fail[0],0,sizeof(fail[0]));
    for(i=1,j=1;i<=n;i++)
    {
        for(int c=s[i]-'0'-1;c>=(i==1);c--)
            if(check(c,i,j)){mx=i;break;}
        if(s[i]!=s[i-1])ext(s[i]-'0',j++);
    }
    if(!mx)
    {
        if(n<=4)return false;
        n--,ans[n]=ans[n-2]=8,ans[n-1]=9;
        for(i=1;i<=n-3;i++)ans[i]=9;
        return true;
    }
    memset(fail[0],0,sizeof(fail[0]));
    for(i=j=1;i<mx;ans[i]=s[i]-'0',i++)
        if(s[i]!=s[i-1])ext(s[i]-'0',j++);
    for(i=mx;i<=n;i++)
    {
        for(int c=i==mx?s[i]-'0'-1:9;c>=(i==1);c--)
            if(check(c,i,j)){ans[i]=c;break;}
        if(j==1||ans[i]!=ans[i-1])ext(ans[i],j++);
    }
    return true;
}
int main()
{
    //freopen("lx.in","r",stdin);
    //freopen("lx.out","w",stdout);
    while(scanf("%s",s+1)!=EOF)
        if(solve())
        {
            for(int i=1;i<=n;i++)putchar('0'+ans[i]);
            putchar('\n');
        }
        else puts("-1");
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值