【题目描述】
有一个的字符串S需要拆分成k个串,每一个串需要花费一些代价来维护。对于一个串,其维护的代价为第i个字符在模式串P中的位置pos的(i – pos) * pos之和。现在需要计算出k个串的最小维护代价。
【输入格式】
第一行一个字符串P和一个整数k。
第二行一个字符串S。
【输出格式】
一行,最小维护代价。
【样例输入】
abcdefghijklmnopqrstuvwzyx 3
aabbbccccccccc
【样例输出】
2
【数据范围】
对于30%的数据 k <= 100,|S| <= 100;
对于100%的数据 1 <= k <= 500,1 <= |S| <= 20000, 模式串P中不存在相同的两个的字符,所有字符都是小写字母。
【样例说明】
拆成“aabbb”,“cccc”和“ccccc”,6->(0 + 0 + 1 + 2 + 3), -4->(-4 -2 + 0 + 2) 和 0->(-4 -2 + 0 + 2 + 4)。
思路
f[i][j]=f[k][j-1]+(sum[i]-sum[k])-(1+k)*(sump[i]-sump[k])
用sump表示(pos[k + 1] + pos[k + 2] + … + pos[i])的前缀和
用sum表示(k * pos[k + 1] + (k + 1) * pos[k + 2] + … + (i - 1) * pos[i] )的前缀和
f[k][j-1]+(sum[i]-sum[k])是不分段的答案
分段后k+1 ~ i左移了k+1个单位,针对每一个位置,减少了(k+1)*pos
所以这个转移方程还是比较合理的
设u < v
u比v优的条件是
(f[u][j-1] - sum[u] + (u+1)*sump[u]) - (f[v][j-1] - sum[v] + (v+1)*sump[v]) < (u-v)*sump[i]
有一点要注意的就是这是个二维的dp,由于j存在于关系式当中,会影响最优的比较,所以选择维护多个队列维护最优。
承接前几道斜率优化的练习,不打详解了,参照斜率优化
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#define LL long long
using namespace std;
const int N = 20010;
LL pos[N], s[N], sum[N], sump[N], sumpp[N], f[N][510], q[N][510];
int head[510], tail[510];
int idc;
int pointx(int u, int v){
return (u - v);
}
LL pointy(int u, int v, int j){
return (f[u][j-1] - sum[u] + (u+1)*sump[u]) - (f[v][j-1] - sum[v] + (v+1)*sump[v]);
}
int main(){
freopen("string.in", "r", stdin);
freopen("string.out", "w", stdout);
int n, k; char cc;
while(scanf("%c", &cc)){
if(cc == ' ') break;
pos[cc - 96] = idc;
idc++;
}
scanf("%d%c", &k, &cc);
idc = 0;
while(scanf("%c", &cc)){
if(cc == '\n') break;
if( !idc ) sump[0] = pos[cc - 96];
if( idc ) sump[idc] = sump[idc-1] + pos[cc - 96];
s[idc] = (idc - pos[cc - 96]) * pos[cc - 96];
if( !idc ) sum[0] = s[idc];
if( idc ) sum[idc] = sum[idc-1] + s[idc];
f[idc][1] = sum[idc];
idc++;
}
n = idc - 1;
for(int i=1; i<=n; i++){
for(int j=2; j<=k; j++){
while(head[j]+1<tail[j] && pointy(q[head[j]][j],q[head[j]+1][j],j) >= pointx(q[head[j]][j],q[head[j]+1][j])*sump[i]) ++head[j];
//a1不比a2优,head++,直到满足head最优
f[i][j] = f[q[head[j]][j]][j-1]+(sum[i]-sum[q[head[j]][j]])-(q[head[j]][j]+1)*(sump[i]-sump[q[head[j]][j]]);
while(head[j]+1<tail[j] && pointy(q[tail[j]-1][j],i,j)*pointx(q[tail[j]-2][j],q[tail[j]-1][j]) <= pointy(q[tail[j]-2][j],q[tail[j]-1][j],j)*pointx(q[tail[j]-1][j],i)) --tail[j];
//维护上凸包
if(q[tail[j]-1][j] != i) q[tail[j]++][j] = i;
}
}
printf("%d\n", f[n][k]);
return 0;
}
上面这个代码不太优,仔细想一想就会发现在我们for循环转移时,可以变为j在外层,i在内层,这样j在转移方程的问题就得到解决了,j++时q数组就可以清空了。这样就不需要二维的q
代码如下
#include<stdio.h>
#include<cstring>
#define clear(a) memset(a,0,sizeof(a));
using namespace std;
const int maxn=20001;
char str[maxn],p[maxn];
int dp[maxn][501],sum[maxn],pos[maxn];
int sump[maxn],q[maxn],h,t;
int lens,lenp,k,j;
inline int up(int k,int p){
return dp[k][j-1]-dp[p][j-1]+sum[p]-sum[k]+(k+1)*sump[k]-(p+1)*sump[p];
}
inline int down(int k,int p){
return k-p;
}
inline void sum_init(){
lens=strlen(str),lenp=strlen(p);
for(register int i=0;i<lenp;i++) pos[p[i]-'a']=i;
for(register int i=0;i<lens;i++){
sum[i]=(i-pos[str[i]-'a'])*pos[str[i]-'a'];
sump[i]=pos[str[i]-'a'];
if(i>=1) sum[i]+=sum[i-1],sump[i]+=sump[i-1];
}
}
int main(){
freopen("string.in","r",stdin);
freopen("string.out","w",stdout);
scanf("%s",p);
scanf("%d",&k);
scanf("%s",str);
sum_init();
for(register int i=0;i<lens;i++) dp[i][1]=sum[i];
for(j=2;j<=k;j++){
h=1,t=0,clear(q);
q[++t]=0;
for(register int i=0;i<lens;i++){
while(h<t&&up(q[h+1],q[h])<=(q[h+1]-q[h])*sump[i]) h++;
int k=q[h];
dp[i][j]=dp[k][j-1]+sum[i]-sum[k]-(k+1)*(sump[i]-sump[k]);
while(h<t&&up(i,q[t])*down(q[t],q[t-1])<=up(q[t],q[t-1])*down(i,q[t])) t--;
q[++t]=i;
}
}
printf("%d",dp[lens-1][k]);
return 0;
}
非笔者亲笔
ps:初始化脑抽,生无可恋【摊手