LOJ2687 BalticOI2013 Vim 线头DP

传送门


多图警告!!!

一种很新奇的\(DP\),全网似乎只有一两篇题解……

首先,序列中的一段\(e\)等价于在跳的过程中这一段\(e\)之后的一个字符必须要经过,并且在最后的答案中加上$2 \times $e的个数。

那么原题等价于:给出一个序列和两种移动方式,移动过程中必须要经过某一些点,求最小代价。

我们不妨把若干连续的\(f\)操作和若干连续的\(h\)操作看成线,那么移动路线就变成下面这样

o_%e5%9b%be%e8%a1%a81.png

首先,考虑下面两种移动路线

o_%e5%9b%be%e8%a1%a82.png

A路线一定没有B路线优,因为A路线有重复的折返。

这样说来:如果经过某些连续的\(f\)操作之后开始进行\(h\)操作,那么一定会到达要到达的最前面的目标,然后一直进行\(f\)操作不再回来。

到这里不难设计出一个暴力的\(DP\):设\(dp_{i,j}\)表示已经经过了前\(i\)个必经字符,当前光标在第\(j\)个字符时的最小代价。设字符集为\(A\),那么这种\(DP\)\(O(N^2A)\)的,不够优秀。考虑优化。

发现上面的条件等价于对于某一个位置\(i\),经过的位置覆盖了位置\(i\)\(i+1\)之间的线段的线的数量要么是\(1\),要么是\(3\),对应下图的\(AB\)两种情况。

o_%e5%9b%be%e8%a1%a83.png

到了这里就可以开始设计更加优秀的\(DP\)

\(p_{i,j}\)表示覆盖了\(i\)\(i+1\)之间的线段\(1\)次,且覆盖\(i\)\(i+1\)之间的线段的\(f\)操作选择的字符是\(j\)的最小代价,\(q_{i,j,k}\)表示覆盖了\(i\)\(i+1\)之间的线段\(3\)次,且在进行\(h\)操作之前覆盖\(i\)\(i+1\)之间的线段的\(f\)操作选择的字符是\(j\)、在进行\(h\)操作之后覆盖\(i\)\(i+1\)之间的线段的\(f\)操作选择的字符是\(k\)的最小代价

又设\(s_i\)表示字符串的第\(i\)个字符,\(imp_i\)表示原串中第\(i\)个字符前是否存在字符\(e\)

转移:

\[\begin{align}p_{i,j} = & p_{i-1,j} & j \neq s_i \&\& imp_i \neq 1\\& p_{i-1,s_i} + 2 \\& q_{i-1,s_i,j} & j \neq s_i \\ & q_{i-1,s_i,s_i} + 2 \end{align}\]

\(p_{i,j}\)的转移分别对应下图的\(ABCD\)情况

其中虚线表示新加入的线,红色字表示对应位置的字符类型,黑色字表示位置编号

o_%e5%9b%be%e8%a1%a84.png

\(\begin{align} q_{i,j,k} = & p_{i-1,j} + 3 & j \neq s_i \\ & p_{i-1,s_i}+5 \\ & q_{i-1,j,k} + 1 & j \neq s_i \&\& k \neq s_i \\ & q_{i-1,s_i,k} + 3 & k \neq s_i \\ & q_{i-1,j,s_i} + 3 & j \neq s_i \\ & q_{i-1,s_i,s_i} + 5 \end{align}\)

\(q_{i,j,k}\)转移分别对应下图中的\(ABCDEF\)情况

o_%e5%9b%be%e8%a1%a85.png

o_%e5%9b%be%e8%a1%a86.png

o_%e5%9b%be%e8%a1%a87.png

o_%e5%9b%be%e8%a1%a88.png

o_%e5%9b%be%e8%a1%a89.png

o_%e5%9b%be%e8%a1%a810.png

可以发现转移就是把线延长和补全的过程,所以叫做线头DP

初始值:\(f_{0,s_1}=0\),其他等于\(inf\)。最后的答案是\(f_{len,x}\),其中\(x\)是没有在字符串中出现过的字符。这可以理解成在无限远的地方有一个字符\(x\),最后一次操作就是直接跳到这一个无限远的地方。当然,这意味着最后的答案会加上跳到这个无限远的地方的\(2\)的代价,减掉\(2\)就行了。

Update:转移\(q\)的时候并不知道为什么D有用,但是不转移会WA

#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
//This code is written by Itst
using namespace std;

inline int read(){
    int a = 0;
    char c = getchar();
    bool f = 0;
    while(!isdigit(c) && c != EOF){
        if(c == '-')
            f = 1;
        c = getchar();
    }
    if(c == EOF)
        exit(0);
    while(isdigit(c)){
        a = a * 10 + c - 48;
        c = getchar();
    }
    return f ? -a : a;
}

const int MAXN = 7e4 + 7 , A = 11;
int f[MAXN][A] , g[MAXN][A][A] , ch[MAXN];
bool must[MAXN];
int N , M , cnt;

inline char getc(){
    char c = getchar();
    while(!islower(c))
        c = getchar();
    return c;
}

int main(){
#ifndef ONLINE_JUDGE
    //freopen("in","r",stdin);
    //freopen("out","w",stdout);
#endif
    N = read();
    bool ife = 1;
    for(int i = 1 ; i <= N ; ++i){
        char c = getc();
        if(c == 'e')
            cnt += (ife = 1);
        else{
            must[++M] = ife;
            ife = 0;
            ch[M] = c - 'a';
        }
    }
    for(int i = 0 ; i < A ; ++i){
        for(int j = 0 ; j < A ; ++j)
            g[0][i][j] = INF;
        f[0][i] = INF;
    }
    f[0][ch[1]] = 0;
    for(int i = 1 ; i <= M ; ++i)
        for(int j = 0 ; j < A ; ++j){
            int t = INF;
            if(j != ch[i] && !must[i])
                t = min(t , f[i - 1][j]);
            t = min(t , f[i - 1][ch[i]] + 2);
            if(j != ch[i])
                t = min(t , g[i - 1][ch[i]][j]);
            t = min(t , g[i - 1][ch[i]][ch[i]] + 2);
            f[i][j] = t;
            for(int k = 0 ; k < A ; ++k){
                t = INF;
                if(j != ch[i])
                    t = min(t , f[i - 1][j] + 3);
                t = min(t , f[i - 1][ch[i]] + 5);
                if(j != ch[i] && k != ch[i])
                    t = min(t , g[i - 1][j][k] + 1);
                if(j != ch[i])
                    t = min(t , g[i - 1][j][ch[i]] + 3);
                if(k != ch[i])
                    t = min(t , g[i - 1][ch[i]][k] + 3);
                t = min(t , g[i - 1][ch[i]][ch[i]] + 5);
                g[i][j][k] = t;
            }
        }
    cout << f[M][10] + 2 * cnt - 2;
    return 0;
}

转载于:https://www.cnblogs.com/Itst/p/10339605.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值