【IOI96】最长前缀
Description
一些生物体的复杂结构可以用其基元的序列表示,而一个基元用一个大写英文字符串表示。生物学家的一个问题就是一个这样的长序列分解为基元(字符串)的序列。对于给定的基元集合P,如果可以从中选出N个基元P1,P2,P3,...,Pn,将它们各自对应的字符串依次连接后得到一个字符串S,称S可以由基元集合P构成。在从P中挑选基元时,一个基元可以使用多次,也可不用。例如,序列 ABABACABAAB 可以由基元集合{A,AB,BA,CA,BBC} 构成。
字符串的前K个字符为该字符串的前缀,其长度为K。请写一个程序,对于输入的基元集合P和字符串T,求出一个可以由基元集合P构成的字符串T的前缀,要求该前缀的长度尽可能长,输出其长度。
Input
输入数据的开头是基元集合P,包括 1..200 个元素(长度为 1..10 )组成的集合,用连续的以空格或者回车键分开的字符串表示。字母全部是大写,数据可能不止一行。元素集合结束的标志是一个只包含一个 “.” 的一行。集合中的元素没有重复。接着是大写字母序列T ,长度为 1..200,000 ,用一行或者多行的字符串来表示。换行符并不是序列T的一部分。
Output
只有一行,输出一个整数,表示基元集合P构成的字符串T的前缀的长度。
Sample Input
A AB BA CA BBC
ABABACABAABC
Sample Output
状态转移方程:f[i]=f[i-len]+len
len是字典中单词的长度,f[i]表示到前i个字符的最长长度。
我先前找的是这样一个方程:f[i]表示用字典中的单词,可否拼出长度为i的字符串。实际操作起来非常繁琐:
1.看第i个字符与它之前的字符(或者它本身)组成的单词是否为字典中的单词,如果是,则f[i]=1,否则执行2;
2.看第i个字符与它之后的字符组成的单词是否为字典中的单词,如果是,则f[i]=1,否则执行3;
3.看第i个字符与它之前、之后的字符共同组成的单词是否在字典中,如果是,则f[i]=1,否则,i-1为最终答案。
可以看到,程序需要考虑的细节非常之多,稍有不慎就会出错。而我也是犯了许多错误之后才能将思路理到如此地步。
可以说,好的转移方程会大大降低编程难度。但是不是所有动态规划都能找到一劳永逸的方程呢?我想不是吧。所以说,
关键是锻炼自己的思维,并做好编复杂程序的心里准备。
1 #include < stdio.h >
2 #include < stdlib.h >
3 #include < string .h >
4 int a[ 11 ][ 201 ],sum[ 11 ] = { 0 };
5 char b[ 201 ][ 10 ];
6 int f[ 200001 ] = { 0 },n;
7 char list[ 200000 ],li[ 20000 ];
8
9 int check( int fr, int l)
10 {
11 int i,j,flag = 1 ;
12 if (sum[l] == 0 ) return 0 ;
13 for (j = 1 ;j <= sum[l];j ++ )
14 {
15 flag = 1 ;
16 for (i = 0 ;i < l;i ++ )
17 {
18 if (list[i + fr] != b[a[l][j]][i])
19 flag = 0 ;
20 }
21 if (flag == 1 ) break ;
22 }
23 return flag;
24 }
25
26 int main(){
27 FILE * in , * out ;
28 int i = 1 ,j,i1 = 0 ,k,l,flag = 0 ,max;
29 in = fopen( " input2.txt " , " r " );
30 out = fopen( " output.txt " , " w " );
31
32 fscanf( in , " %s " ,b[ 1 ]);
33 while (b[i][ 0 ] != ' . ' )
34 {
35 l = strlen(b[i]);
36 a[l][ ++ sum[l]] = i;
37 fscanf( in , " %s " ,b[ ++ i]);
38 }
39 while (fscanf( in , " %s " ,li) != EOF)
40 strcat(list,li);
41 n = strlen(list);
42
43 for (i = 1 ;i <= n;i ++ )
44 {
45 for (k = 1 ;k <= 10 ;k ++ )
46 if (i >= k)
47 {
48 flag = check(i - k,k);
49 if (flag && f[i - k] || flag && i - k == 0 )
50 f[i] = k + f[i - k];
51 if(max<f[i])max=f[i];
52 }
53 }
54
55 fprintf( out , " %d\n " ,max);
56 fclose( in );
57 fclose( out );
58 return 0 ;
59 }
60
check(fr,l)函数是用来检查题目给出的字符串中从fr到fr+l-1的范围之内的字符所组成的单词是否
在字典中,是则返回1.b[i]存储字典中第i个单词。a[i][j]存储长度为i的第j个字典中的单词,为的
是节省查找时间。最终答案是f[i]中的最大值。
其实程序难度本来不大,但是对具体单词起止位置的考虑花去了太多时间,究其原因是对位置不敏感,
又没采取直观方式去分析。下次分析此类问题,还是该画图啊。