1. 程式人生 > 其它 >7屆藍橋杯省賽-密碼脫落

7屆藍橋杯省賽-密碼脫落

前言:

  在這一篇文章中,我們主要介紹“密碼脫落”這道題的解法

正文:

一、題幹

  時間限制:1.0s   記憶體限制:256.0MB

  X星球的考古學家發現了一批古代留下來的密碼。
  這些密碼是由A、B、C、D 四種植物的種子串成的序列。
  仔細分析發現,這些密碼串當初應該是前後對稱的(也就是我們說的映象串)。
  由於年代久遠,其中許多種子脫落了,因而可能會失去映象的特徵。
  你的任務是:
  給定一個現在看到的密碼串,計算一下從當初的狀態,它要至少脫落多少個種子,才可能會變成現在的樣子。
  輸入一行,表示現在看到的密碼串(長度不大於1000)
  要求輸出一個正整數,表示至少脫落了多少個種子。


  例如,輸入:
  ABCBA
  則程式應該輸出:
  0
  再例如,輸入:
  ABECDCBABC
  則程式應該輸出:
  3
  資源約定:
  峰值記憶體消耗 < 256M
  CPU消耗 < 1000ms

 

二、思路:

  什麼是子序列以及迴文序列:

  ABCBAE為一個序列

  而ABE則為一個子序列(保持原有元素順序)

  BCB則是一個迴文子序列

  ABCBA則是最長迴文子序列

 

  題目簡單概括來說就是要算出現在的串新增多少個字元就可以成為原串。

  要求最少新增幾個字元,我們可以先從原串中找到一個最長迴文子序列,然後對於原串中不屬於這個迴文子序列的字元,在它關於迴文子序列中心的對稱位置新增一個相同字元即可。(見下樣例)

 

  那麼需要新增的字元數量即為len-最長迴文子序列長度。

  (len為輸入的字串長度)

 

  舉個例子:

  輸入

  ABECDCBABC

  可以找到除黑色字型以外的為最長迴文子序列。

  ABECDCBABC

  那麼補齊之後的字串則為

  CBABECDCEBABC

 

  而補上的CBE就是題目中所說的脫落的密碼了,我們求出脫落的密碼數量即可。

 

  那麼問題就很簡單了,求出最長迴文子序列就可以了

  方法:動態規劃

  分為兩種情況考慮:

  第一個元素和最後一個元素相同,則有:(n為字串長度)

  lps(0,n-1)=lps(1,n-2)+2

  (lps為Longest Palindromic Subsequence,最長迴文子序列)

  不相同時,則有:

  lps(0,n-1) = max(lps(1,n-1), lps(0,n-2))

  也就是說可以把問題拆分開成小問題,最後使用遞迴就能得出最長迴文子序列的長度。

  不過這樣做的話會超時(指用lps函式遞迴),只能得到33分。

 

  既然用遞迴會超時,就得用其他方法了,但是核心思想還是沒變,程式碼如下

 1 #include<iostream>
 2 #include<string>
 3 #include<cstring>
 4 using namespace std;
 5 
 6 int main(int argc, char const *argv[])
 7 {
 8     char s[1020];
 9     cin>>s;
10     int len = strlen(s);
11     int dp[len][len];
12     memset(dp,0,sizeof(dp));
13     for (int i = 0; i < len; i++)
14     {
15         dp[i][i] = 1;
16     }
17     
18     for (int i = len-1; i >= 0; i--)
19     {
20         for (int j = i+1; j < len; j++)
21         {
22             if (s[i] == s[j])
23             {
24                 dp[i][j] = dp[i+1][j-1] + 2;
25             }
26             else
27             {
28                 dp[i][j] = max(dp[i+1][j],dp[i][j-1]);
29             }
30         }
31         
32     }
33     
34     cout<<len-dp[0][len-1];
35 
36     return 0;
37 }

  程式碼解讀:

  dp[i][j]可以理解為s[i]到s[j]這段範圍內的最長迴文子序列的長度,其中↓這一部分就是核心程式碼,用於求dp[i][j]的值。

 1     for (int i = len-1; i >= 0; i--)
 2     {
 3         for (int j = i+1; j < len; j++)
 4         {
 5             if (s[i] == s[j])
 6             {
 7                 dp[i][j] = dp[i+1][j-1] + 2;
 8             }
 9             else
10             {
11                 dp[i][j] = max(dp[i+1][j],dp[i][j-1]);
12             }
13         }
14         
15     }

  中間的if-else結構很易懂,就是前面講的兩種情況,舉例子來看更容易理解。

  假設輸入ABECDCBABC,進入迴圈時i=9, j=10, j>len跳出,i--,i=8時正式進入迴圈

0

1

2

3

4

5

6

7

8

9

A

B

E

C

D

C

B

A

B

C

 

 

 

 

 

 

 

 

i

j

  開始時的dp陣列:

1

0

0

0

0

0

0

0

0

0

0

1

0

0

0

0

0

0

0

0

0

0

1

0

0

0

0

0

0

0

0

0

0

1

0

0

0

0

0

0

0

0

0

0

1

0

0

0

0

0

0

0

0

0

0

1

0

0

0

0

0

0

0

0

0

0

1

0

0

0

0

0

0

0

0

0

0

1

0

0

0

0

0

0

0

0

0

0

1

0

0

0

0

0

0

0

0

0

0

1

  結束時的dp陣列:(標紅的為行列)

1

1

1

1

1

3

5

7

7

7

0

0

1

1

1

1

3

5

5

5

5

1

0

0

1

1

1

3

3

3

3

5

2

0

0

0

1

1

3

3

3

3

5

3

0

0

0

0

1

1

1

1

3

5

4

0

0

0

0

0

1

1

1

3

5

5

0

0

0

0

0

0

1

1

3

3

6

0

0

0

0

0

0

0

1

1

1

7

0

0

0

0

0

0

0

0

1

1

8

0

0

0

0

0

0

0

0

0

1

9

0

1

2

3

4

5

6

7

8

9

 

  簡單來說就是從dp[8][9]一路算dp[7][8],dp[7][9],dp[6][7],dp[6][8],dp[6][9]。。算到dp[0][7],dp[0][8],dp[0][9]結束,dp[0][9]就是答案。

後記:

  感謝各位讀到這,越做感覺自己越菜了~