1. 程式人生 > 其它 >hdu 4632 Palindrome Subsequence

hdu 4632 Palindrome Subsequence

技術標籤:動態規劃

題目連結
定義:dp[i] [j]為字串第i個字元到第j個字元所含迴文子序列的數量

思路:此題用了區間DP的方法。先從小問題開始考慮,由於已知一個由單個字母組成的字串他的子字串必定只有一個,對於每一個字串,我們要讓他的規模擴大都需要在他的頭或尾加上一個字母,所以我們可以將問題從單字元組成的字串擴大到任意長度的字串。因此,在我們求一個字串有多少個迴文子序列的時候,可以將問題拆分為求兩個次大的字串,即最長字串去掉頭尾的子字串的迴文子序列的問題。
在這裡插入圖片描述
對於字串IJ,由於去掉開頭第一個元素和末尾第一個元素所造成的影響是不同的,所以dp[i][j] = dp[i+1][j]+dp[i

][j-1],同時我們發現,藍色部分 i+1~j-1在兩端區間均有出現,因此這段區間是重複的。此時如果s[i]!=s[j],那麼這段藍色區間確實是重複的,需要減去一段。但是如果s[i]==s[j],那麼可以確定每一個藍色區域的子序列加上s[i]和s[j]後都會是一個迴文的序列,這種加上兩頭仍然是迴文子序列的序列個數就等於藍色部分迴文子序列的個數加上一(1是藍色部分不選,只選擇s[i],s[j]的情況),此時不用減去這一段。因此,最終的狀態轉移方程為

f ( x ) = { d p [ i + 1 ] [ j ] + d p [ i ] [ j − 1 ] − d p [ i + 1 ] [ j − 1 ]        s[i]!=s[j] d p [ i + 1 ] [ j ] + d p [ i ] [ j − 1 ] + 1        s[i]==s[j] f(x)=\left\{ \begin{aligned} dp[i+1][j]+dp[i][j-1]-dp[i+1][j-1]\:\:\:\:\:\:& \text{s[i]!=s[j]}\\ dp[i+1][j]+dp[i][j-1] +1\:\:\:\:\:\:& \text{s[i]==s[j]} \end{aligned} \right.

f(x)={dp[i+1][j]+dp[i][j1]dp[i+1][j1]dp[i+1][j]+dp[i][j1]+1s[i]!=s[j]s[i]==s[j]
題目要求對10007取模,在式子中應注意這一點,有減法的式子中應該加上一個10007避免得到負數。

程式碼:

#include<cmath>
#include<stack>
#include<cstdio>
#include<queue>
#include<vector> 
#include<map>
#include<set>
#include<iostream>
#include<string> #include<sstream> #include<algorithm> #include<string.h> typedef long long ll; using namespace std; inline int read() { int s = 0, w = 1; char ch = getchar(); while (ch < '0' || ch>'9') { if (ch == '-')w = -1; ch = getchar(); } while (ch >= '0' && ch <= '9') s = s * 10 + ch - '0', ch = getchar(); return s * w; }//快讀 const int M = 1005; const int MOD = 10007; int dp[M][M]; int main() { int T; string s; scanf("%d",&T); getchar(); for(int t=1;t<=T;t++){ memset(dp,0,sizeof(dp)); getline(cin,s); int ans=0,n=s.length(); for(int i=0;i<n;i++) dp[i][i]=1; for(int len=1;len<n;len++){ for(int i=0;i+len<n;i++){ int j=i+len; if(s[i]==s[j]) dp[i][j]=(dp[i][j-1]+dp[i+1][j]+1)%MOD; else dp[i][j]=(dp[i][j-1]+dp[i+1][j]-dp[i+1][j-1]+MOD)%MOD; } } printf("Case %d: %d\n",t,dp[0][n-1]); } return 0; }