UVa 10617 Again Palindrome(经典回文串区间DP)
题意:
给定一个字符串s,对s进行删除操作,使得剩下的子串是回文字符串,问最多有多少种这种子串。
思路:
涉及到回文字符串,首先要想到的肯定是区间DP,如何写出状态转移方程?
直接从题意切入:dp[i, j]表示区间[i, j]最多有多少个这样的子串。
1. s[i] == s[j] 去掉s[i],则一个子问题就是dp[i+1, j],去掉s[j],另一个子问题就是dp[i, j-1]。
显然这两个子问题是会有重叠的,比如去掉s[i], s[j]的dp[i+1, j-1]。
如果s[i],s[j]都不去掉呢?则这个子问题的解显然是dp[i+1, j-1] + 1。
于是会有 dp[i][j] = dp[i+1][j] + dp[i][j-1] + 1;
2. s[i] != s[j] 此时的子问题就比上述要简单了,因为s[i] s[j]与dp[i+1, j-1]的回文子串构成不了回文串。
于是 dp[i][j] = dp[i+1][j] + dp[i][j-1] - dp[i+1][j-1];
总结:动态规划变化多端,不同的问题可能会有不同的切入点,一开始切入题目的时候可以采用最直接的按照题意来。
无果之后再去思考一些见解的方法。这也算是渐进的一种方式了吧,首先不要试图把问题想的太复杂。
#include <cstdio> #include <cstdlib> #include <cstring> #include <cmath> #define max(a, b) (((a) > (b)) ? (a) : (b)) const int MAXN = 65; char str[MAXN]; long long int dp[MAXN][MAXN]; int main() { int cases; scanf("%d", &cases); while (cases--) { scanf("%s", str); int n = strlen(str); memset(dp, 0, sizeof(dp)); for (int i = 0; i < n; ++i) { dp[i][i] = 1; if (str[i] == str[i+1]) dp[i][i+1] = 3; else dp[i][i+1] = 2; } for (int p = 2; p < n; ++p) { for (int i = 0, j = p; j < n; ++i, ++j) if (str[i] == str[j]) dp[i][j] = dp[i+1][j] + dp[i][j-1] + 1; else dp[i][j] = dp[i+1][j] + dp[i][j-1] - dp[i+1][j-1]; } printf("%lld\n", dp[0][n-1]); } return 0; }
kedebug
Department of Computer Science and Engineering,
Shanghai Jiao Tong University
E-mail: kedebug0@gmail.com
GitHub: http://github.com/kedebug
-------------------------------------------------------