经典DP模型--回文词--IOI2000

【问题描述】
回文词是一种对称的字符串——也就是说, 一个回文词, 从左到右读和从右到左读得到的结果是一样的。 任意给定一个字符串, 通过插入若干字符, 都可以变成一个回文词。 你的任务是写一个程序, 求出将给定字符串变成回文词所需插入的最少字符数。
比如字符串“ Ab3bd”,在插入两个字符后可以变成一个回文词( “ dAb3bAd” 或“ Adb3bdA”) 。然而,插入两个以下的字符无法使它变成一个回文词。
【输入文件】
第一行包含一个整数N,表示给定字符串的长度, 3<=N<=5000
第二行是一个长度为N的字符串,字符串由大小写字母和数字构成。
【输出文件】
一个整数,表示需要插入的最少字符数。
【输入样例】
5
Ab3bd
【输出样例】
2
【问题分析】
所谓回文词(正着读和反着读一样) ,其实就是从中间断开把后面翻转后与前面部分一样(注意奇数
和偶数有区别) 。例:
回文词: AB3BA
断开: AB BA (奇数个时去掉中间字符)
翻转: AB AB
这个题目要求出最少填几个数可以使一个字符串变成回文词, 也就是说从任意点截断, 再翻转后面部分后。两个序列有相同的部分不用添字符,不一样的部分添上字符就可以了。例:
回文词: Ab3bd
截断: Ab bd
翻转: Ab db
b在两个序列里都有,在第二个里添A在第一个里添d就可以了:Adb Adb
这样添两个就可以了,
显然从别的地方截断添的个数要比这样多。这样就把原问题抽象成求最长公共子序列问题了。 枚举截断点, 把原串截断, 翻转。 求最长公共子序列。答案就是len-( ans*2) len是翻转后两个序列的长度和。 Ans 是最长公共子序列的长度。

我们注意到,另一半的LCS是和这一半的LCS相同的(另一半是指后半段不翻转,前半段翻转的两个字符串)。由此,整个字符串的LCS一定是 >=  2*ans的。

这样的话,直接把原串翻转作为第二个序列和第一个序列求最长公共子序列就可以了。这样最后求解就不用乘2了,也不用枚举截断点了例:
原串: Ab3bd
翻转: db3bA
最长公共子序列b3b
添加2个字符
怎么理解这个优化呢?
其实翻转了序列后字符的先后顺序就变了, 求解最长公共子序列中得到的解, 是唯一的, 也就是说这个序列的顺序是唯一的, 如果在翻转后的序列和原串能得到相同的序列, 那么这个序列在两个串中字符间的顺序是横定的,着就满足了回文词的定义(正着读和反着读一样) 。所以这个优化是正确的。

除了直接进行LCS的求解,更直接、变化更多的解法是进行状态划分,分析状态转移关系

原问题出发, 找这个问题的子问题。 和上面说的最长公共子序列问题一样, 设计序列的问题我们一般要考虑它的子序列,也就是更短的序列。
考虑边界条件
显然单独的字符就是边界了,而且单独的字符就是回文词,添加0个字符就可以了。
如果是两个字符组成的序列怎么办呢?
只要看他们是否相同就可以了, 如果相同那就是回文词了, 添加0个字符, 如果不相同就在它的左边
或右边添一个字符,让另外一个当对称轴。
如果是3个字符呢?
我们用S存这个序列,如果S[1]=S[3]那么它就是回文词了,
如果S[1]<>S[3]那么就在前面添S[3]或后面添S[1]
剩下的就要考虑S[1]S[2]和S[2]S[3]这两个序列了。
通过前面的分析我们很容易想到这样的算法:
对于一个序列S只要看它的左右端的字符是否相同, 如果相同那么就看除掉两端字符的新串要添的字
符个数了;如果不同,就在它左面添上右断的字符然后考虑去掉新序列两端的字符后的串要添的字符。 或
者在右面添上左端的字符,在考虑去掉添了字符后新串左右两端字符得到的新串要添的字符。
设计一个二维状态opt[L,i]表示长度是L+1, 起点是i的序列变成回文词要添的字符的个数。 阶段就是
字符的长度,决策要分类,即S[i] 和S[i+L]是否相等。
状态转移方程:

opt[L,i]   =min(opt[L-1,i]+1,opt[L-1,i+1]+1)                         (s[i]!=s[i+L])

               =min(opt[L-1,i]+1,opt[L-1,i+1]+1,opt[L-2,i+1])    (s[i]==s[i+L])

复杂度:
空间复杂度=状态数O( N2)
时间复杂度=状态数O( N2)* 转移代价O( 1)=O( N2)


扩展题目:

调整队形 (queue.pas/c/cpp) 来源: TJU P1006
【问题描述】
学校艺术节上,规定合唱队要参加比赛,各个队员的衣服颜色不能很混乱:合唱队员应排成一横排,
且衣服颜色必须是左右对称的。
例如:“ 红蓝绿蓝红” 或“ 红蓝绿绿蓝红” 都是符合的,而“ 红蓝绿红” 或“ 蓝绿蓝红” 就不符合要求。
合唱队人数自然很多,仅现有的同学就可能会有 3000个。老师希望将合唱队调整得符合要求,但想
要调整尽量少,减少麻烦。以下任一动作认为是一次调整:
1、在队伍左或右边加一个人(衣服颜色依要求而定) ;
2、在队伍中任两个人中间插入一个人(衣服颜色依要求而定) ;
3、剔掉一个人;
4、让一个人换衣服颜色;
老师想知道就目前的队形最少的调整次数是多少,请你编一个程序来回答他。
因为加入合唱队很热门, 你可以认为人数是无限的, 即随时想加一个人都能找到人。 同时衣服颜色也
是任意的。
【输入文件】
第一行是一个整数n(1≤n≤3000)。
第二行是n个整数,从左到右分别表示现有的每个队员衣服的颜色号,都是1到3000的整数。
【输出文件】
一个数,即对于输入队列,要调整得符合要求,最少的调整次数。
【输入样例】
5
122 43
【输出样例】
2
【问题分析】
读完题目发现很熟悉,仔细想想这个题就是回文词的加强版。不同与回文词的是这个问题的决策多了,不仅可以插入一个人(词) ,还可以剔人,还可以换服装,其实剔人和插入是等价的。

也就是说比原问题只多了一个条件就是可以换服装。
这样就不能用回文词的第一个方法解了。( 因为序列中的元素不固定, 可以换) 。 只能用第二个方法解。
和回文词一样,阶段是序列的长度,状态是 opt[i,j]表示[i,j]这段区间内要变成回文所需要的最少的调
整次数。
决策比回文词多了一个, 即: 如果左右两端不一样还可以通过换服装这种方式只花费一次的代价调整
好。
状态转移方程:


opt[i,j]= min{opt[i,j-1]+1,opt[i+1,j]+1,opt[i+1,j-1]+1}(a[i] != a[j],1<=i<j<=n)
             = min{opt[i,j-1]+1,opt[i+1,j]+1,opt[i+1,j-1]}   (a[i] == a[j],1<=i<j<=n)


边界条件:opt[i,i]=0 (1<=i<=n)
时间复杂度:
状态数O( N2)*转移代价O( 1)=总复杂度O( N2)


posted @ 2017-06-01 21:18  Pic  阅读(457)  评论(0编辑  收藏  举报