简单题sol
\(RT\)
\(Cyber \_Tree\) 出了一道简单题。。。大概会成为签到题。。。。
题意
已知一个字符串 \(S\) ,要求构造一个长度为 \(l\) 的字符串 \(T\) ,若 \(T\) 中一个长度为 \(len\) 子串是 \(S\) 的前缀,则可以获得 \(a_{len}\) 的分数,条件是这个子串是极长的,即不存在 \(u \subseteq v\) ,且 \(v\) 也是 \(S\) 的前缀。
题解
发现题中前缀出现的很刻意,不难发现这题要烤的是 \(kmp+dp\) 。
于是对 \(S\) 跑 \(kmp\) ,很自然的设出状态, \(f_{i, j}\) 表示长度为 \(i\) 的字符串,当前结尾与 \(S_j\) 匹配时的最大价值。
转移很显然,对于每一种状态,枚举接下来选 \(26\) 个小写字母可以转移到哪种状态。。。
\(f_{i+1, to_{j, ch}}= max f_{i, j} + a_{to_{j, ch}}\)
有一点要注意,如果从 \(f_{i,j}\) 转移到 \(f_{i+1, j+1}\) ,要先减去 \(a_j\) ,再加上 \(a_{j+1}\) 。
于是您就顺利切掉了这道题。。。
code
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=510;
const ll INF=0x3f3f3f3f3f3f3f3f;
inline int read(){
int f=1, x=0; char ch=getchar();
while(!isdigit(ch)) { if(ch=='-') f=-1; ch=getchar(); }
while(isdigit(ch)) { x=x*10+ch-48; ch=getchar(); }
return f*x;
}
int n, l, a[N], ul[N], nxt[N], to[N][26];
char ch[N];
ll f[2][N], ans=-INF;
signed main(void){
n=read(), l=read();
scanf("%s", ch+1);
for(int i=1; i<=n; ++i) a[i]=read();
for(int i=1; i<=n; ++i) ul[i]=a[i]-a[i-1];
for(int i=2, j=0; i<=n; ++i){
while(j&&ch[j+1]!=ch[i]) j=nxt[j];
if(ch[j+1]==ch[i]) ++j; nxt[i]=j;
}
for(int i=0; i<=n; ++i) for(int j=0; j<26; ++j){
if(ch[i+1]==j+'a') to[i][j]=i+1;
else to[i][j]=to[nxt[i]][j];
}
memset(f, -0x3f, sizeof f);
f[0][0]=0;
for(int i=0; i<l; ++i){
memset(f[(i&1)^1], -0x3f, sizeof f[(i&1)^1]);
for(int j=0; j<=n; ++j) for(int k=0; k<26; ++k){
int t=to[j][k];
if(t==j+1) f[(i&1)^1][t]=max(f[(i&1)^1][t], f[i&1][j]+ul[t]);
else f[(i&1)^1][t]=max(f[(i&1)^1][t], f[i&1][j]+a[t]);
}
}
for(int i=0; i<=n; ++i) ans=max(ans, f[l&1][i]);
printf("%lld\n", ans); return 0;
}
总结
此题稍有点板,具有一定的思维,考察了对于 \(kmp\) 的熟练程度与理解程度。
完全没有码量,也不考验码力,想到就能切,属于大家都喜欢的 \(dp\) 题。。。
并不是很难,适合作为一场娱乐赛的签到题。
也可能存在其他更优解法,但是出题人太菜了并没有想到,欢迎比赛结束后来 殴打出题人 与出题人一起讨论。
并没有查重,有重题纯属巧合。。。。