原题: http://codeforces.com/problemset/problem/1120/C
题意:
给一个字符串,你可以将字符串分成很多个部分,这个部分在前面出现过的话(可以是多个部分并起来后,其中的字符串),那么这个部分花费可以变成b。否则,你只能选择一个长度为1的部分,其花费为a。求最小花费。
解析:
首先,你需要处理出所有的子串在之前是否出现过: 是否出现在 中。
开始对于每个height为单位做处理,发现一个bug:一个串只会和排名最近的串比较,因为可能有重叠的原因,这个串可能不是最优解。
改为每个后缀往两边做height的递减操作。注意长度有三个限制:之前串是否在当前串前,之前串到当前串的长度,当前串到结尾长度。
#include<bits/stdc++.h>
using namespace std;
const int N = 5009;//max(用到字符,字符串长度),如果字符串的组成是数字的话注意上限
#define rep(i,a,b) for(register int i=a;i<=b;i++)
char x[N];
int wa[N],wb[N],wv[N],Ws[N];
int cmp(int *r,int a,int b,int l){
return r[a]==r[b]&&r[a+l]==r[b+l];
}
/* str,sa,len+1,max(ASCII)+1 */
void da(const char r[],int sa[],int n,int m){
int i,j,p,*x=wa,*y=wb,*t;
for(i=0; i<m; i++) Ws[i]=0;
for(i=0; i<n; i++) Ws[x[i]=r[i]]++;//以字符的ascii码为下标
for(i=1; i<m; i++) Ws[i]+=Ws[i-1];
for(i=n-1; i>=0; i--) sa[--Ws[x[i]]]=i;
for(j=1,p=1; p<n; j*=2,m=p){
for(p=0,i=n-j; i<n; i++) y[p++]=i;
for(i=0; i<n; i++) if(sa[i]>=j) y[p++]=sa[i]-j;
for(i=0; i<n; i++) wv[i]=x[y[i]];
for(i=0; i<m; i++) Ws[i]=0;
for(i=0; i<n; i++) Ws[wv[i]]++;
for(i=1; i<m; i++) Ws[i]+=Ws[i-1];
for(i=n-1; i>=0; i--) sa[--Ws[wv[i]]]=y[i];
for(t=x,x=y,y=t,p=1,x[sa[0]]=0,i=1; i<n; i++)
x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;
}
}
int sa[N],Rank[N],height[N];
//求height数组
/* str,sa,len */
void calheight(const char *r,int *sa,int n){
int i,j,k=0;
for(i=1; i<=n; i++) Rank[sa[i]]=i;
for(i=0; i<n; height[Rank[i++]]=k)
for(k?k--:0,j=sa[Rank[i]-1]; r[i+k]==r[j+k]; k++);
for(int i=n;i>=1;--i) ++sa[i],Rank[i]=Rank[i-1];
}
char str[N];
bool vis[N][N];
int dp[N];
int main(){//字符串下标读入时0~len-1,height数组1~len
int n,a,b;scanf("%d%d%d",&n,&a,&b);
scanf("%s",str);
int len=strlen(str);
da(str,sa,len+1,130); //sa
calheight(str,sa,len); //height rank
rep(i,2,len){
int r=Rank[i];
int h=0;
int mi=1e9;
while(height[r]){
mi=min(mi,height[r]);
int id=sa[r-1];
if(id>i){
r--;continue;
}
h=max(h,min(mi,min(len-i+1,i-id)));
r--;
}
r=Rank[i]+1;
mi=1e9;
while(r<=len&&height[r]){
mi=min(mi,height[r]);
int id=sa[r];
if(id>i){
r++;continue;
}
h=max(h,min(mi,min(len-i+1,i-id)));
r++;
}
rep(j,i,i+h-1){
vis[i][j]=1;
}
}
/* 错误代码
rep(i,2,len){
if(height[i]){
int x=sa[i],y=sa[i-1];
int h=min(height[i],min(len-y+1,y-x));
rep(l,1,h){
rep(j,y,n){
int r=j+l-1;
if(r>n)break;
vis[j][r]=1;
}
}
}
}*/
memset(dp,0x3f,sizeof dp);
dp[0]=0;
rep(i,0,n-1){
dp[i+1]=min(dp[i+1],dp[i]+a);
rep(j,i+1,n){
if(vis[i+1][j]){
dp[j]=min(dp[j],dp[i]+b);
}
else break;
}
}
printf("%d\n",dp[n]);
}
别人家的思路
直接LCS求出 和 两个前缀的公共后缀长度,再dp。
#include<bits/stdc++.h>
using namespace std;
char x[5009];
int dp[5009];
int lcs[5009][5009];
int main(){
int n,a,b;
scanf("%d%d%d",&n,&a,&b);
scanf("%s",x+1);
for(int i=1;i<=n;i++){
dp[i]=dp[i-1]+a;
for(int j=1;j<i;j++){
lcs[i][j]=(x[i]==x[j]?lcs[i-1][j-1]+1:0);
int len=min(lcs[i][j],i-j);
dp[i]=min(dp[i],dp[i-len]+b);
}
}
printf("%d\n",dp[n]);
}