【NOIP 模拟题】[T2]拯救紫萱学姐(kmp+树形dp)
拯救紫萱学姐
时间限制:1 s 内存限制:256 MB
【题目描述】
其实在开考前半个小时题面并不是这样的。
由于明天要考试,同学们要把抽屉里的书都搬空,书很多而且办了走读不能回寝室的学长一眼就看到了回班撩他的学姐,于是就把学姐当学长用♂了:“帮我把这摞书搬走OvO”。
学姐筋疲力尽地抱着沉重的一摞书回到了机房,出于无聊她翻开了学长的字典。
学长的字典由一个字符串组成。对于两个字符串a和b,如果a既是b的前缀也是b的后缀,那么称a和b“相似”,空字符串和任何字符串相似。一个字符串可以通过编辑变换成一个比它短而且与它相似的字符串,付出的代价为这两个字符串的长度之差的平方。两个字符串通过编辑而变为同一个字符串所花费的最小代价被称为最短编辑距离。
给定学长的字典,现在学姐想知道这个字符串的每一对前缀的最短编辑距离中的最大值是多少。请你帮助劳累的紫萱学姐解决这个问题。
【输入格式】
一个字符串,仅包含小写英文字母。
【输出格式】
这个字符串每一对前缀中最长的最短编辑距离。
【样例输入】
abcab
【样例输出】
23
【提示】
最短编辑距离最长的一对前缀是abca和abcab,最短变换过程如下(箭头中的数字为过程的代价)
“abca”-9->“a”-1->(空字符串)
“abcab”-9->“ab”-4->“”
对于40%的数据,n≤500。
对于70%的数据,n≤5000。
对于100%的数据,n≤1000000,n为字符串长度。
【题解】【kmp+树形dp】
【先用kmp求出字符串的失配函数,由于要最短编辑距离最大,那么,每次更新的最短距离一定是更新到当前点的失配位置。然后,按照这个来建树,每个点为一个前缀序列,每个点只能向上连一条边,就连到它的失配位置,而每个点可以向下连多条边,因为当前点可能由多个点转移过来。边权为当前前缀的末位置和它失配的末位置的差的平方。然后,那么,本题实际就是求一条最长链,即求树的直径。用dfs或树形dp均可实现】
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
char ch[1000010];
int a[1000010],nxt[1000010],p[1000010],tot;
int fail[1000010],len;
ll val[1000010],f[1000010],g[1000010],ans;
inline void add(int x,int y,ll v)
{
tot++; a[tot]=y; nxt[tot]=p[x]; p[x]=tot; val[tot]=v;
}
inline void kmp()
{
int i,j;
fail[0]=-1;
for(i=0;i<len;++i)
{
j=fail[i];
while(j!=-1&&ch[j]!=ch[i]) j=fail[j];
fail[i+1]=j+1;
}
return;
}
void dfs(int x,int fa)
{
for(int i=p[x];i!=-1;i=nxt[i])
if(a[i]!=fa)
{
dfs(a[i],x);
if(f[a[i]]+val[i]>f[x])
{
g[x]=f[x];
f[x]=f[a[i]]+val[i];
}
else g[x]=max(g[x],f[a[i]]+val[i]);
}
ans=max(ans,f[x]+g[x]);
}
int main()
{
freopen("savemzx.in","r",stdin);
freopen("savemzx.out","w",stdout);
int i,j;
memset(p,-1,sizeof(p));
memset(nxt,-1,sizeof(nxt));
scanf("%s",ch);
len=strlen(ch);
kmp();
for(i=1;i<=len;++i) add(fail[i],i,(ll)(i-fail[i])*(i-fail[i]));
dfs(0,-1);
printf("%I64d\n",ans);
}