【CTSC2016】香山的树

· · 题解

题面

写晕了写晕了,肝了一个下午加半个晚上,人都做懵了,幸亏BZOJ和UOJ都是跑最快的,不然感觉巨亏。

感觉思路不简单,代码也超级难写。

这题其实和【WC2004】孪生项链超级像,只不过这题代码更难写,也更恶心一点。

我们考虑数位DP。

怎么写DP?

从前往后扫描每个位置,从大到小枚举每个字符,计算以当前确定部分为前缀的方案总数。

那么如果方案总数>=k,说明这个位置不用弄了,直接进入下个位置。否则让k-=方案总数,去枚举下个字符。

接下来重点就是如何记录方案总数,想法当然就是之前提过的数位DP。

f[i][j][k]表示第i位已匹配的j位未匹配k位的方案总数。

枚举当前使用的字符,用KMP转移,由于已经知道了最大匹配长度,那么当前添加的字符就不能超过最大匹配的下一位。

事实上,已经确定的位置无需再计算方案总数,所以我们从未知的位置开始就行了。

而第i位的匹配情况只与第i-1位的匹配情况有关,所以这完全可以滚动一下。

具体转移以及各种细节参见代码吧。

code:

//2018.8.3 by ljz
#include<bits/stdc++.h>
using namespace std;
#define res register LL
#define LL long long
#define inf 0x3f3f3f3f
#define eps 1e-15
inline LL read(){
    res s=0;
    bool w=0;
    char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')w=1;ch=getchar();}
    while(ch>='0'&&ch<='9')s=s*10+ch-'0',ch=getchar();
    return w?-s:s;
}
inline void _swap(res &x,res &y){
    x^=y^=x^=y;
}
inline LL _abs(const res &x){
    return x>0?x:-x;
}
inline LL _max(const res &x,const res &y){
    return x>y?x:y;
}
inline LL _min(const res &x,const res &y){
    return x<y?x:y;
}
const LL C=30,CH=26,N=50+10;
const char max_char=CH+'a'-1;
LL n,k,len;
LL pre[N],next[N][C],minchar[N],ret[N];
LL f[2][N][N],dp[N][N];
char s[N<<1];
inline bool judge(){
    for(res i=0;i<len;i++)s[i+len]=s[i];
    for(res i=1;i<len;i++){
        res j;
        for(j=0;j<len;j++)if(s[j]!=s[i+j])break;
        if(j==len||s[i+j]<s[j])return 0;
    }
    return 1;
}
inline bool inc(){
    while(len&&s[len-1]==max_char)len--;
    if(!len)return 0;
    s[len-1]++;
    return 1;
}
inline LL get_tot(){
    memset(next[0],0,sizeof(next[0]));
    pre[0]=-1;
    minchar[0]=0;
    for(res i=0;i<len;i++){
        res c=s[i]-'a';
        if(c<minchar[i])return 0;
        minchar[i]=c;
        next[i][c]=i+1;
        if(pre[i]==-1)pre[i+1]=0;
        else pre[i+1]=next[pre[i]][c];
        minchar[i+1]=minchar[pre[i+1]];
        for(res j=0;j<CH;j++)next[i+1][j]=next[pre[i+1]][j];
    }
    for(res i=0;i<=len;i++){
        ret[i]=0;
        res j=i;
        for(res k=0;k<len;k++){
            if(s[k]-'a'<minchar[j]){ret[i]=-1;break;}
            j=next[j][s[k]-'a'];
            if(j==len)ret[i]++;
        }
    }
    memset(f,0,sizeof(f));
    memset(dp,0,sizeof(dp));
    f[len&1][pre[len]][0]=1;
    for(res i=len;i<=n;i++){
        memset(f[(i+1)&1],0,sizeof(f[(i+1)&1]));
        for(res j=0;j<len;j++)
            for(res k=0;k<=n-len;k++){
                if(ret[j]>=0)dp[i][k+ret[j]]+=f[i&1][j][k];
                if(i==n)continue;
                if(f[i&1][j][k]){
                    if(j+1==len)f[(i+1)&1][pre[len]][k+1]+=f[i&1][j][k];
                    else f[(i+1)&1][j+1][k]+=f[i&1][j][k];
                    f[(i+1)&1][0][k]+=f[i&1][j][k]*(CH-1-minchar[j]);
                }
            }
    }
    for(res i=pre[len];i;i=pre[i]){
        if(ret[len-i]>=0)
            for(res j=2;j*(len-i)<=n;j++)
                if(j*(len-i)>=len)dp[j*(len-i)][j]--;
        if((len-i)*2<len)i=(i-1)%(len-i)+1;
    }
    for(res i=len;i<=n;i++)
        for(res j=1;j<=i;j++)
            if(dp[i][j])for(res k=2;i*k<=n;k++)dp[i*k][j*k]-=dp[i][j];
    res ans=0;
    for(res i=len;i<=n;i++)
        for(res j=1;j<=i;j++)
            if(dp[i][j])ans+=dp[i][j]/j;
    return ans;
}
int main(){
    n=read(),k=read();
    scanf("%s",s);
    len=strlen(s);
    while(233){
        res tot=get_tot();
        if(tot<k){
            k-=tot;
            if(!inc()){puts("-1");return 0;}
        }
        else {
            if(judge())
                if(--k==0)break;
            s[len++]='a';
        }
    }
    for(res i=0;i<len;i++)putchar(s[i]);
    return 0;
}