LCS应用(2)—整理队形
前面说了那么多,你可能都没有地方提交你打的代码。下面这道题是LCS的又一应用,原题见rqnoj P478。
下面是题目描述。
【问题描述】(rqnoj478) 学校艺术节上,规定合唱队要参加比赛,个个队员的衣服颜色不能很混乱:合唱队员应排成一横排,且衣服颜色必须是左右队称的。 例如:“红蓝绿蓝红”或“红蓝绿绿蓝红”都是符合的,而“红蓝绿红”或“蓝绿蓝红”就不符合。 合唱队人数自然很多,仅现有的同学就可能会有3000个。老师希望将合唱队调整的符合要求,但要尽量调整的少,减少麻烦。仅有一下3种情况算作是一次调整: 1.在队伍中插入一个人; 2.剔掉一个人; 3.让一个人衣服换颜色。 老师想知道目前的队形下,调整的符合要求需要最少多少次操作。 因为合唱队很热门,所以你可以认为人数是无限的,即想加入一个人时,总有人加。同时衣服的颜色也是任意的。 【输入格式】 第一行是一个整数n(1<=n<=3000) 第二行是n个整数,从左到右表示现有的每个队员每个人的衣服颜色编号,都是不大于3000的自然数。 【输出格式】 一个数,即最少调整次数。 【样例输入】 5 1 2 2 4 3 【样例输出】 2
这是一道区间类动态规划的题目。这道题与回文数很像,都是对已知序列进行操作,是它“对称”。但是这道题有三种操作方法,插入、删除和改变。插入操作和删除操作可以看做是等价的(即达到的效果相同),唯一与回文词不同的就是它多了改变这一操作。进过简单的思考可以发现,“改变”的实质是在已知序列的基础上将一对不匹配的字符转换成匹配的。我们定义f[i,j]表示要让队列从第i个到第j个左右对称需要的操作次数。那么,改变一个相当于f[i,j]:=f[i+1,j-1]+1。所以动态方程就可以推出:
另外,当我们初始化的时候,只用把j>i的部分赋予极大值,因为只有这里存储的是区间最小值。
参考代码:
1 program queue;
2 var
3 f:array[0..3000,0..3000]of longint;
4 a:array[1..3000]of integer;
5 n,i,j,l:integer;
6 function min(x,y:longint):longint;
7 begin
8 if x<y then exit(x)
9 else exit(y);
10 end;
11 begin
12 readln(n);
13 for i:=1 to n do read(a[i]);
14 for i:=1 to n do
15 for j:=i+1 to n do
16 f[i,j]:=100000000; //初始化f数组
17 for l:=1 to n-1 do
18 for i:=1 to n do
19 begin
20 j:=i+l;
21 if j>n then break;
22 if a[i]=a[j] then f[i,j]:=f[i+1,j-1] //a[i]=a[j],无需操作
23 else f[i,j]:=f[i+1,j-1]+1; //假设进行改变操作,并入下面的min也可
24 f[i,j]:=min(min(f[i,j],f[i+1,j]+1),f[i,j-1]+1); //找到区间最优解
25 end;
26 writeln(f[1,n]);
27 end.
下面这道题和整理队形很相似,它提供的是另一种初始化的方法,另外题目加上了一些字串处理的内容,有兴趣的同学可以做一做。
【题目描述】 回文串是一个从前读和从后读一样的字符串。比如ABBA,MOM是回文串,但MATE不是。一个回文串可以通过修改某些位置变成一个回文串。如果一个字符串通过修改不超过k个位置变成一个回文串,那么这个字符串就被称为k回文串。一个最长并且是k回文串的子串被称为最长k回文子串。 【输入】 第一行一个字符串(长度不超过1000)和一个非负整数k(0<=k<=字符串长度)。字符串只包含’a’到‘z’。 【输出】输出最长k回文子串的长度。 【输入输出样例1】 Palindrome.in Palindrome.out abba 0 4 【输入输出样例2】 Palindrome.in Palindrome.out mate 1 3 【输入输出样例3】 Palindrome.in Palindrome.out zabcddcbxy 1 8
这道题与整理队形一样,唯一的不同就是对于这个给定的串只能进行“改变”操作,不能进行“删除”或者“添加”。这样题目就更加简单了。
另外提一下参考代码中的初始化。它先把制作长度为2的回文子串所需要的步数通过一个简单的枚举计算出来,就免的赋极大值再从头去找。因为长度为2的情况已经找过了,只要从长度为3的情况开始更新f数组即可。
我在处理字符串的时候是一个字符一个字符读入的,操作的相对麻烦,参考代码中我改成了相对方便的ansistring。
参考代码:
1 program palindrome;
2 var
3 f:array[0..1001,0..1001]of longint;
4 a:array[1..1000]of char;
5 i,j,n,k,po,t,l,max:longint;
6 s:ansistring;
7 temp:string;
8 c:char;
9 begin
10 readln(s);
11 i:=pos(' ',s); //处理读入的字串
12 temp:=copy(s,i+1,length(s)-i);
13 delete(s,i,length(s)-i+1);
14 val(temp,k,i);
15 n:=length(s);
16 for i:=1 to n-1 do //枚举长度为2的情况
17 if s[i]<>s[i+1] then f[i,i+1]:=1;
18 for l:=3 to n do //从3开始更新(p.s.注意这里不是n-1)
19 for i:=1 to n-l+1 do
20 begin
21 j:=i+l-1;
22 if s[i]=s[j] then f[i,j]:=f[i+1,j-1]
23 else f[i,j]:=f[i+1,j-1]+1;
24 end;
25 max:=0;
26 for i:=1 to n do
27 for j:=i to n do
28 if(f[i,j]<=k)and(max<j-i+1)then max:=j-i+1; //寻找达到条件的最大值
29 writeln(max);
30 end.
(saltless原创,转载请注明出处)