• LOJ2687 BalticOI2013 Vim 线头DP


    传送门


    多图警告!!!

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

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

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

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

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

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

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

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

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

    到了这里就可以开始设计更加优秀的(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)

    转移:

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

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

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

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

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

    可以发现转移就是把线延长和补全的过程,所以叫做线头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;
    }
    
  • 相关阅读:
    02-05 flutter provider的使用
    02-04 flutter 构造函数总结
    02-03 flutter异步
    02-02dart语法
    02-01dart语法
    01-01 iOS内存对齐、内存对齐算法
    01-02 iOS kvo、kvc
    01-03 category 原理概述
    多线程编程 (2) -NSOperation
    IOS基本控件属性
  • 原文地址:https://www.cnblogs.com/Itst/p/10339605.html
Copyright © 2020-2023  润新知