区间dp小结
區間dp,顧名思義,就是在區間上dp,即把整個區間劃分為一個個的小區間,在小區間內dp求出最優值,然后把這些小區間合并以后就是整個取件的最優值。
下面是一些比較經典的區間dp題目:
1.NYOJ 737 石子合并:http://acm.nyist.net/JudgeOnline/problem.php?pid=737
題意:有n堆石子,每堆有a[i]個,每次合并時只能合并相鄰的兩堆,代價為兩堆石子的個數之和。問把這n堆石子合并成一堆需要的最小代價是多少。
狀態:dp[i][j] 表示合并第 i 堆到第 j 堆石子的最小代價
轉移方程;dp[i][j] = min(dp[i][j], dp[i][k] + dp[k+1][j] + sum[j] - sum[i-1]);
其中sum[i]表示前i堆石子的總個數。
#include<stdio.h> #include<string.h> #include<algorithm> using namespace std; #define INF 0x3fffffff const int N = 205; int a[N], sum[N], dp[N][N];int main() {int n, i, j;while(~scanf("%d",&n) && n) {sum[0] = 0;for(i = 1; i <= n; i++) {scanf("%d",&a[i]);dp[i][i] = 0;sum[i] = sum[i-1] + a[i];}for(int l = 2; l <= n; l++) { //枚舉合并的堆數for(i = 1; i <= n - l + 1; i++) { //枚舉起始點j = i + l - 1;dp[i][j] = INF;for(int k = i; k <= j; k++) //枚舉中間分隔點dp[i][j] = min(dp[i][j], dp[i][k] + dp[k+1][j] + sum[j] - sum[i-1]);}}printf("%d\n",dp[1][n]);}return 0; }
2.POJ 2955 Brackets?http://poj.org/problem?id=2955
題意:給出一串由‘(‘,’)‘,’[',‘]’組成的字符串,問最多有多少個括號匹配。
狀態:dp[i][j]表示 i 到 j 最多的匹配個數
轉移方程:dp[i][j] = max(dp[i][j], dp[i][k] + dp[k+1][j]);
#include<stdio.h> #include<string.h> #include<algorithm> using namespace std; const int N = 105; char str[N]; int dp[N][N]; bool judge(char a, char b) {if(a == '(' && b == ')') return true;if(a == '[' && b == ']') return true;return false; } int main() {while(gets(str) != NULL){if(!strcmp(str, "end")) break;int len = strlen(str);for(int i = 0; i < len; i++){dp[i][i] = 0;if(judge(str[i], str[i+1]))dp[i][i+1] = 2;elsedp[i][i+1] = 0;}for(int k = 2; k < len; k++) //枚舉子串的長度{for(int i = 0; i + k < len; i++){int r = i + k;dp[i][r] = 0;if(judge(str[i], str[r]))dp[i][r] = dp[i+1][r-1] + 2;for(int j = i; j < r; j++)dp[i][r] = max(dp[i][r], dp[i][j] + dp[j+1][r]);}}printf("%d\n",dp[0][len-1]);}return 0; }3.POJ 1141 Brackets Sequence http://poj.org/problem?id=1141
題意:給出一串由‘(‘,’)‘,’[',‘]’組成的字符串,求使原串里面的括號全部匹配后的長度最短的字符串。
狀態:dp[i][j] 表示使 i 到 j 全部匹配最少需要添加的括號個數。
轉移方程:dp[i][j] = min(dp[i][j], dp[i][k] + dp[i+1][k]);
#include<stdio.h> #include<string.h> #define INF 0x3ffffff const int N = 105; char str[N]; int dp[N][N], flag[N][N];bool judge(char a, char b) {if(a == '(' && b == ')') return true;if(a == '[' && b == ']') return true;return false; }void print(int i, int j) {if(i > j) return ;if(i == j){if(str[i] == '[' || str[i] == ']') printf("[]");else printf("()");}else if(flag[i][j] == -1){printf("%c",str[i]);print(i+1, j-1);printf("%c",str[j]);}else{print(i, flag[i][j]);print(flag[i][j] + 1, j);} }int main() {while(gets(str) != NULL){int len = strlen(str);memset(dp, 0, sizeof(dp));for(int i = 0; i < len; i++)dp[i][i] = 1;for(int i = 1; i < len; i++){for(int l = 0; l + i < len; l++){int r = l + i;dp[l][r] = INF;if(judge(str[l], str[r])){if(dp[l][r] > dp[l+1][r-1])dp[l][r] = dp[l+1][r-1], flag[l][r] = -1; //flag[l][r]=-1表示str[l]與str[j]匹配時最優}for(int k = l; k < r; k++)if(dp[l][r] > dp[l][k] + dp[k+1][r])dp[l][r] = dp[l][k] + dp[k+1][r], flag[l][r] = k; //flag[l][r]=k表示以k為分割點的兩端的匹配之和最優}}//printf("%d\n",dp[0][len-1]);print(0, len-1);printf("\n");}return 0; }總結