简单题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\) 题。。。
并不是很难,适合作为一场娱乐赛的签到题。

也可能存在其他更优解法,但是出题人太菜了并没有想到,欢迎比赛结束后来 殴打出题人 与出题人一起讨论。

并没有查重,有重题纯属巧合。。。。

posted @ 2021-11-03 10:32  Cyber_Tree  阅读(45)  评论(0编辑  收藏  举报