正好学校考试考到过一个加强版,写一写。
Solution
很明显的DP
如果 (n=0) ,设 (f_i) 表示到 (i) 位置之前不同的子序列数,可以得到两个转移方程:
-
第 (i) 位的字符之前没有出现过,方程就是 (f_i=2 imes f_{i-1}+1)
意思是有 (i-1) 位时的方案,和 ({i-1}) 位的每一种方案都加第 (i) 位上的字符,还有自己 (1) 种
-
第 (i) 位的字符出现过,方程是 (f_{i}=2 imes f_{i-1}-f_{pre_{a_i}})
其中 (pre_{a_i}) 是第 (i) 位字符上次出现的位置,这个方程的意思是要减去上次这个字符的贡献
现在 (n>0) ,因为要最大数量,所以第二个方程中 (f_{pre_{a_i}}) 减的越少越好,那么填的时候按 (pre_{a_i}) 从小到大依次填入最优
此时的复杂度是 (O(n+m)) ,完全足够通过本题
但是,如果是 (nleq 10^{18})
这个时候,上面的方法就完全不行了
我们发现在填 (n) 的时候,每 (k) 位都算是一个循环节,而 (kleq 26)
我们可以考虑用一些和 (k) 有关且能快速求出答案的方法——矩阵快速幂
将矩阵初始化的时候考虑第 (i) 个字符和第 (j) 个字符之间的相互影响即可
时间复杂度: (O(m+k^3log n))
注意:运算的时候没加空集,输出答案的时候记得加上
Code
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int mod=1e9+7,N=4e6+10;
int m,k,a[N],vis[N],q[N],cnt,f[N],pre[N],tot,pow2[N];
ll n,t,ans,d;
char s[N];
inline int add(int x,int y){return x+y>mod?x+y-mod:x+y;}
inline int dec(int x,int y){return x-y<0?x-y+mod:x-y;}
struct matrix{
int c[210][210];
void init(int d=0){
for(int i=1;i<=k+1;i++)
for(int j=1;j<=k+1;j++)
c[i][j]=0;
for(int i=1;i<=k+1;i++) c[i][i]=d;
}
matrix operator * (matrix x){
matrix res;
res.init();
for(int i=1;i<=k+1;i++)
for(int j=1;j<=k+1;j++)
for(int l=1;l<=k+1;l++)
res.c[i][j]=add(res.c[i][j],1ll*c[i][l]*x.c[l][j]%mod);
return res;
}
}Ans;
matrix Fpow(matrix a,ll b){
matrix res;
res.init(1);
while(b){
if(b&1) res=res*a;
a=a*a;
b>>=1;
}
return res;
}
int main(){
scanf("%lld%d",&n,&k);cnt=k;
scanf("%s",s+1);
m=strlen(s+1);
for(int i=1;i<=m;i++)
a[i]=s[i]-'a'+1;
for(int i=m;i>=1;i--)
if(!vis[a[i]]) q[--cnt]=a[i],vis[a[i]]=1;
for(int i=1;i<=k;i++)
if(!vis[i]) q[--cnt]=i;
memset(pre,-1,sizeof(pre));
t=min(n,1ll*k);
for(int i=1;i<=m+t;i++){
if(i>m) a[i]=q[tot],tot=(tot+1)%k;
if(pre[a[i]]!=-1) f[i]=dec(add(f[i-1],f[i-1]),f[pre[a[i]]-1]);
else f[i]=add(add(f[i-1],f[i-1]),1);
pre[a[i]]=i;
}
if(n==t){
printf("%d
",f[n+m]+1);
return 0;
}
Ans.init();
pow2[0]=1;
for(int i=1;i<=k+1;i++) pow2[i]=pow2[i-1]*2%mod;
Ans.c[1][k+1]=1;
for(int i=2;i<=k+1;i++){
for(int j=1;j<i-1;j++)
Ans.c[i][j]=dec(mod,pow2[i-j-1]);
Ans.c[i][i-1]=mod-1;
Ans.c[i][k+1]=add(Ans.c[i][k+1],pow2[i-1]);
}
d=(n-1)/k,n-=d*k;
Ans=Fpow(Ans,d);
for(int i=0;i<=k;i++)
ans=add(ans,1ll*f[m+i]*Ans.c[n+1][i+1]%mod);
printf("%d
",ans+1);
return 0;
}