string 字符串拆分 二维dp(斜率优化)

【题目描述】
有一个的字符串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:初始化脑抽,生无可恋【摊手

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值