跳蚤

跳蚤

 跳蚤

很久很久以前,森林里住着一群跳蚤。一天,跳蚤国王得到了一个神秘的字符串,它想进行研究。

首先,他会把串分成不超过 k 个子串,然后对于每个子串 S,他会从 S 的所有子串中选择字典序最大的那一个,并在选出来的 k 个子串中选择字典序最大的那一个。他称其为“魔力串”。现在他想找一个最优的分法让“魔力串”字典序最小。

第一行一个整数 $k \le 15$
接下来一个长度不超过 $10^5$ 的字符串 


Sol

建sam,求出总共有多少子串。

可以二分答案k,然后求出第k大的串。

贪心从后往前取,遇到不合法就开新的一段,最后判断段数是否<=k

这样一定是对的。遇到不合法的串再怎么加字符也不合法。

遇到这种答案有单调性的可以选择二分。

#include<cstdio>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>
#define maxn 2000005
#define ll unsigned long long
#define mod 1000000007
using namespace std;
int n,num,rt=1,la=1,cnt=1;
int ord[maxn],sz[maxn],top;
ll sum[maxn],hash[maxn],ha[maxn],p[maxn];
char ch[maxn],a[maxn];
struct node{
    int nex[26],par,Max;
}s[maxn*2];
void ins(int c){
    int np=++cnt,p=la;la=np;
    s[np].Max=s[p].Max+1;
    for(;p&&!s[p].nex[c];p=s[p].par)s[p].nex[c]=np;
    if(!p)s[np].par=rt;
    else {
        int q=s[p].nex[c];
        if(s[q].Max==s[p].Max+1)s[np].par=q;
        else {
            int nq=++cnt;
            s[nq].par=s[q].par;s[nq].Max=s[p].Max+1;
            for(int i=0;i<26;i++)s[nq].nex[i]=s[q].nex[i];
            s[q].par=s[np].par=nq;
            for(;p&&s[p].nex[c]==q;p=s[p].par)s[p].nex[c]=nq;
        }
    }
}
void calc(){
    int tax[maxn];
    for(int i=1;i<=cnt;i++)tax[s[i].Max]++;
    for(int i=1;i<=cnt;i++)tax[i]+=tax[i-1];
    for(int i=1;i<=cnt;i++)ord[tax[s[i].Max]--]=i;
    for(int i=2;i<=cnt;i++)sz[i]=1;sz[1]=0;
    for(int i=cnt;i>=1;i--){
        int k=ord[i];
        for(int j=0;j<26;j++)sum[k]+=sum[s[k].nex[j]];
        sum[k]+=sz[k];
    }
}
void get(ll x){
    top=0;
    int k=rt;
    while(1){
        if(x<=sz[k])break;
        for(int i=0;i<26;i++){
            if(sum[s[k].nex[i]]+sz[k]>=x){
                a[++top]=i+'a';x-=sz[k];
                k=s[k].nex[i];break;
            }
            else x-=sum[s[k].nex[i]];
        }
    }
    for(int i=1;i<=top;i++)ha[i]=ha[i-1]*mod+a[i];
}
bool check(int li,int ri){
    int len=ri-li+1;
    int l=0,r=min(len,top);
    while(l<r){
        int mid=l+r+1>>1;
        if(hash[li+mid-1]-hash[li-1]*p[mid]==ha[mid])l=mid;
        else r=mid-1;
    }
    if(l==top&&l==len)return 1;
    if(l==top)return 0;
    if(l==len)return 1; 
    return a[l+1]>ch[li+l];
    //a>li~ri  1  else 0
}
bool pd(){
    int la=n,co=0;
    for(int i=n;i>=1;i--){
        if(!check(i,la)){
            if(la==i)return 0;
            la=i,co++;i++;
        } 
        if(co>num)return 0;
    }
    if(co+1>num)return 0;
    return 1;
}
int main(){
    scanf("%d",&num);
    scanf("%s",ch+1);n=strlen(ch+1);p[0]=1;
    for(int i=1;i<=n;i++){
        ins(ch[i]-'a');
        hash[i]=hash[i-1]*mod+ch[i];
        p[i]=p[i-1]*mod;
    }
    calc();
    ll l=1,r=sum[1];
    while(l<r){
        ll mid=l+r>>1;
        get(mid);
        if(pd())r=mid;
        else l=mid+1; 
    }
    get(l);
    for(int i=1;i<=top;i++)printf("%c",a[i]);
    return 0;
}
View Code

 

posted @ 2019-04-07 21:24 liankewei123456 阅读( ...) 评论( ...) 编辑 收藏
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值