前言:這兩天沒有寫什么題目,把前兩周做的有些意思的背包題和最長遞增、公共子序列寫了個總結。反過去寫總結,總能讓自己有一番收獲......就區間dp來說,一開始我完全不明白它是怎么應用的,甚至於看解題報告都看不明白,而到了現在,遇到區間dp之類的題目,我不至於沒有任何方向,慢慢的推導,有些題目沒有自己想象的那么難,還是可以推導出轉移方程的,有些題目,在自己推導過后,與解題報告相對照,也總能有一番全新的收獲。我是覺得,解題報告需要看,但是怎么看,如何看,卻是值得思量.......
1、Light oj 1422 Halloween Costumes
題意:給你n天需要穿的衣服的樣式,每次可以套着穿衣服,脫掉的衣服就不能再用了(可以再穿),問至少要帶多少條衣服才能參加所有宴會
http://lightoj.com/volume_showproblem.php?problem=1422
思路:首先dp[i][j]代表從區間i到區間j需要的最少穿衣服數量,我采取的是從下向上更新的。那么,面臨第i件衣服,首先我們考慮穿上它,那么它所在的區間dp[i][j]=dp[i+1][j]+1;
接着考慮是否可以不用穿上它?在什么條件下,可以不用穿這件衣服呢?只有當區間i+1~~j里面已經穿過這件衣服的時候,就可以考慮不用再穿這件衣服。那么假設i+1<=k<=j
其中第k件衣服與第i件一樣,那么第k件衣服穿上,第i件衣服不穿的情況的最少穿衣數==dp[i+1][k-1]+dp[k][j];第i件衣服不穿,那么就從第i+1件衣服開始計算,第k件衣服存在,那么在這個斷點,我們該怎么判斷是dp[i+1][k-1]+dp[k][j]呢?可以直接從數據推導出這個關系,也可以這樣,在第k區間的時候,a[k]可能不是它自己本身穿的,而是在第k區間到第j區間,存在一個k<tmp<=j,有a[tmp]==a[k]......所以會是dp[i+1][k-1]+dp[k][j]........
#include<iostream> #include<stdio.h> #include<string.h> using namespace std; int min(int x,int y) { if(x>y) return y; else return x; } int dp[105][105],a[105]; int main() { int text,h=0; scanf("%d",&text); while(text--) { int n; scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%d",&a[i]); memset(dp,0,sizeof(dp)); for(int i=1;i<=n;i++) dp[i][i]=1; for(int i=n-1;i>=1;i--) { for(int j=i+1;j<=n;j++) { dp[i][j]=dp[i+1][j]+1; for(int k=i+1;k<=j;k++) if(a[i]==a[k]) { dp[i][j]=min(dp[i][j],dp[i+1][k-1]+dp[k][j]); } } } printf("Case %d: %d\n",++h,dp[1][n]); } return 0; }
2、 poj2955(括號匹配問題)
題意:給你一串()[]括號,要你求出這串括號的最大匹配個數,如'('與')'匹配,為2個,'['與']'匹配,為2個,其他不能匹配.......
思路:dp[i][j]代表從區間i到區間j所匹配的括號的最大個數,首先,假設不匹配,那么dp[i][j]=dp[i+1][j];然后查找i+1~~j有木有與第i個括號匹配的
有的話,dp[i][j]=max(dp[i][j],dp[i+1][k-1]+dp[k][j]+2).....
#include<iostream> #include<stdio.h> #include<string.h> using namespace std; int dp[105][105]; char a[105]; int max(int x,int y) { if(x>y) return x; else return y; } int main() { while(scanf("%s",a+1)>0&&a[1]!='e') { a[0]=2; int len=strlen(a); len--; //printf("%d\n\n",len); //for(int i=1;i<=len;i++) //printf("%d %c\n",i,a[i]); memset(dp,0,sizeof(dp)); for(int i=len-1;i>=1;i--) { for(int j=i+1;j<=len;j++) { dp[i][j]=dp[i+1][j]; for(int k=i+1;k<=j;k++) { if((a[i]=='('&&a[k]==')')||(a[i]=='['&&a[k]==']')) { dp[i][j]=max(dp[i][j],dp[i+1][k-1]+dp[k][j]+2); //printf("%d %d %c %c\n",i,k,a[i],a[k]); } } } } printf("%d\n",dp[1][len]); } return 0; }
3、poj3280(推薦)
題意:給你m個字符,其中有n種字符,每種字符都有兩個值,分別是增加一個這樣的字符的代價,刪除一個這樣的字符的代價,讓你求將原先給出的那串字符變成回文串的最小代價。
思路:dp[i][j]代表區間i到區間j成為回文串的最小代價,那么對於dp[i][j]有三種情況:
1、dp[i+1][j]表示區間i到區間j已經是回文串了的最小代價,那么對於s[i]這個字母,我們有兩種操作,刪除與添加,對應有兩種代價,dp[i+1][j]+add[s[i]],dp[i+1][j]+del[s[i]],取這兩種代價的最小值;
2、dp[i][j-1]表示區間i到區間j-1已經是回文串了的最小代價,那么對於s[j]這個字母,同樣有兩種操作,dp[i][j-1]+add[s[j]],dp[i][j-1]+del[s[j]],取最小值
3、若是s[i]==s[j],dp[i+1][j-1]表示區間i+1到區間j-1已經是回文串的最小代價,那么對於這種情況,我們考慮dp[i][j]與dp[i+1][j-1]的大小........
然后dp[i][j]取上面這些情況的最小值.........
#include<iostream> #include<stdio.h> #include<string.h> using namespace std; int dp[2005][2005],add[27],del[27]; char s[2005]; int min(int x,int y) { if(x>y) return y; else return x; } int main() { int n,m; while(scanf("%d%d",&n,&m)>0) { scanf("%s",s+1); s[0]=2; for(int i=1;i<=n;i++) { char ch[10]; int tmp1,tmp2; scanf("%s%d%d",ch,&tmp1,&tmp2); add[ch[0]-'a'+1]=tmp1; del[ch[0]-'a'+1]=tmp2; } memset(dp,0,sizeof(dp)); for(int i=m-1;i>=1;i--) { for(int j=i+1;j<=m;j++) { dp[i][j]=min(dp[i+1][j]+add[s[i]-'a'+1],dp[i+1][j]+del[s[i]-'a'+1]); int tmp=min(dp[i][j-1]+add[s[j]-'a'+1],dp[i][j-1]+del[s[j]-'a'+1]); dp[i][j]=min(dp[i][j],tmp); if(s[i]==s[j]) dp[i][j]=min(dp[i][j],dp[i+1][j-1]); } } printf("%d\n",dp[1][m]); } return 0; }
4、poj1141(區間dp記錄路徑問題)
題意:給出一串括號,要你補上最少的括號使這一串括號都匹配........
思路:dp[i][j]表示從區間i到區間j使其所以括號匹配需要補上的最少括號數,那么當出現一個括號時,首先考慮它不與后面匹配的情況,那么需要加一個相對應的括號,讓之匹配dp[i][j]=dp[i+1][j]+1;
然后再考慮,若是后面有括號可以讓它匹配的情況,那么假設i<k<=j,當s[i]=='('&&s[k]==')'的時候,考慮動態轉移,dp[i][j]=dp[i+1][k-1]+dp[k][j]-1
為什么這個動態方程減1呢,因為我將與之匹配的那個括號重新計算了一次,當s[k]==')'的時候,在計算dp[k][k]的時候,我的狀態轉移已經把這個括號自動匹配了一次,所以要減去這次匹配的........
然后就是記錄路徑了,開一個二維數組a[i][j],當a[i][j]==-1的時候,表示dp[i][j]這個狀態是從dp[i+1][j]推導過來的,當a[i][j]>0的時候,表示dp[i][j]是從dp[i+1][a[i][j]-1]以及dp[k][j]這兩個狀態推導過來的,那么注意到當a[i][j]!=-1的時候,就正好表示s[i]與s[a[i][j]]匹配,說明在第i個括號這個地方只需要輸出它自己本身,其他的,若是a[i][j]==-1的,都需要輸出它自身以及與它自身匹配的括號.........
#include<iostream> #include<stdio.h> #include<string.h> using namespace std; int dp[300][300],a[300][300],b[300]; char s[300]; void print(int i,int j) { if(i>=j) return; if(a[i][j]==-1) print(i+1,j); if(a[i][j]>0) { b[i]=1; b[a[i][j]]=1; print(i+1,a[i][j]-1); print(a[i][j],j); } } int main() { while(gets(s+1)>0) //這里注意,有直接輸入\n的情況......... { s[0]=2; memset(dp,0,sizeof(dp)); memset(a,-1,sizeof(a)); memset(b,0,sizeof(b)); int len=strlen(s); len--; for(int i=1;i<=len;i++) dp[i][i]=1; for(int i=len-1;i>=1;i--) { for(int j=i+1;j<=len;j++) { dp[i][j]=dp[i+1][j]+1; a[i][j]=-1; for(int k=i+1;k<=j;k++) if((s[i]=='('&&s[k]==')')||(s[i]=='['&&s[k]==']')) { if(dp[i][j]>dp[i+1][k-1]+dp[k][j]-1) { dp[i][j]=dp[i+1][k-1]+dp[k][j]-1; a[i][j]=k; } } } } print(1,len); for(int i=1;i<=len;i++) { if(b[i]==1) printf("%c",s[i]); else { if(s[i]=='('||s[i]==')') printf("()"); else printf("[]"); } } printf("\n"); } return 0; }
5、poj1651(推薦)
題意:給你一組數字,第一個和最后一個數字不可以取出去,其它任意取出去,當你要取出一個數字時,它有一個代價,這個代價就是與它相鄰的兩個數的乘積,求除了首位兩位數字,把其他數字都取出來,它們的代價之和的最小值........
思路:這題目,只有自己做過才能體會......我是說不出來......
Sample Input 6 10 1 50 50 20 5 Sample Output 3650
#include<iostream> #include<stdio.h> #include<string.h> using namespace std; int dp[105][105],a[105]; int min(int x,int y) { if(x>y) return y; else return x; } int main() { int n; while(scanf("%d",&n)>0) { for(int i=1;i<=n;i++) scanf("%d",&a[i]); memset(dp,0,sizeof(dp)); for(int i=n-2;i>=1;i--) { dp[i][i+2]=a[i]*a[i+1]*a[i+2]; for(int j=i+3;j<=n;j++) { dp[i][j]=a[i]*a[i+1]*a[j]+dp[i+1][j]; dp[i][j]=min(dp[i][j],a[i]*a[j-1]*a[j]+dp[i][j-1]); for(int k=i+2;k<j-1;k++) dp[i][j]=min(a[i]*a[k]*a[j]+dp[i][k]+dp[k][j],dp[i][j]); } } printf("%d\n",dp[1][n]); } return 0; }
6、poj3661(推薦)
題意:給你一個n,m,n表示有n分鍾,每i分鍾對應的是第i分鍾能跑的距離,m代表最大疲勞度,每跑一分鍾疲勞度+1,當疲勞度==m,必須休息,在任意時刻都可以選擇休息,如果選擇休息,那么必須休息到疲勞度為0,當然,當疲勞度為0的時候也是可以繼續選擇休息的,求在n分鍾后疲勞度為0所跑的最大距離
思路:dp[i][j]表示在第i分鍾疲勞度為j的時候所跑的最大距離,dp[i][j]=dp[i-1][j-1]+d[i];這個轉移,說的是,第i分鍾選擇跑步,當然,第i分鍾可以選擇不跑步,那么就是選擇休息,題目說了,選擇休息的話必須要休息到疲勞度為0才可以跑,那還有一點,當疲勞度為0了,還是選擇繼續休息呢?dp[i][0]=dp[i-1][0];
選擇休息,那么疲勞度到0了,這一點的最大距離怎么做呢?dp[i][0]=max(dp[i][0],dp[i-k][k]) (0<k<=m&&i-k>0)
這樣所有的狀態都考慮完了,可以寫代碼了.......
#include<iostream> #include<stdio.h> #include<string.h> using namespace std; int dp[10005][505],a[10005]; int main() { int n,m; while(scanf("%d%d",&n,&m)>0) { for(int i=1;i<=n;i++) scanf("%d",&a[i]); memset(dp,0,sizeof(dp)); for(int i=1;i<=n;i++) { for(int j=1;j<=m;j++) dp[i][j]=dp[i-1][j-1]+a[i]; dp[i][0]=dp[i-1][0]; for(int k=1;k<=m;k++) if(i-k>=0) dp[i][0]=max(dp[i][0],dp[i-k][k]); } printf("%d\n",dp[n][0]); } return 0; }
7、hdu2476(推薦)
給出兩串字符,要你將第一串字符變成第二串字符,每次可以改變連續的一個或多個字符,求最少的修改次數
思路:還是區間dp問題,說起來也挺簡單的,只是記錄路徑問題,有些難以想到..........
#include<iostream> #include<stdio.h> #include<string.h> using namespace std; int dp[105][105],a[105]; char s[105],t[105]; int main() { while(scanf("%s",s+1)>0) { scanf("%s",t+1); s[0]=t[0]=2; int len=strlen(s); len--; memset(dp,0,sizeof(dp)); memset(a,0,sizeof(a)); for(int i=1;i<=len;i++) dp[i][i]=1; for(int i=len-1;i>=1;i--) { for(int j=i+1;j<=len;j++) { //if(s[i]!=t[i]) dp[i][j]=dp[i+1][j]+1; for(int k=i+1;k<=j;k++) if(t[i]==t[k]) { dp[i][j]=min(dp[i][j],dp[i+1][k-1]+dp[k][j]); } } } for(int i=1;i<=len;i++) { a[i]=dp[1][i]; if(s[i]==t[i]) { if(i==1) a[i]=0; else a[i]=a[i-1]; } else for(int j=1;j<i;j++) a[i]=min(a[i],a[j]+dp[j+1][i]); } printf("%d\n",a[len]); //printf("%d\n",dp[1][len]); } return 0; }
8、zoj3537(最優三角划分)
9、編輯距離問題
題目描述:
要求兩字符串有差異的字符個數。例如:
aaaaabaaaaa
aaaaacaabaa
這兩個字符串,最大公共字串長度是5,但它們只有兩個字符不同,函數輸出值應為2。
如果是:
aaabbbcccddd
aaaeeeddd
函數的輸出值應該是6。
比較形象地形容一下,把兩個字符串排成上下兩行,每個字符串都可以在任何位置插入空格以便上下對齊,每個列上至少有一個字符來自這兩個字符串。當對齊程度最高的時候,沒有對上的列的數即為函數輸出值。
aaabbbcccddd
aaaeeeddd
最優對齊狀態是:
aaabbbcccddd
aaaeee ddd
沒有對上的列是6,函數輸出值為6。
如果是:
abcde
acefg
最優對齊狀態是:
abcde
a c efg
沒有對上的列數是4,函數輸出值為4。
問題抽象歸類:(編輯距離問題)
設A和B是2個字符串。要用最少的字符操作將字符串A轉換為字符串B。這里所說的字符操作包括:
(1)刪除一個字符;
(2)插入一個字符;
(3)將一個字符改為另一個字符。
將字符串A變換為字符串B所用的最少字符操作數稱為字符串A到B的編輯距離,記為d(A,B)。試設計一個有效算法,對任給的2個字符串A和B,計算出它們的編輯距離d(A,B)。
要求:
輸入:第1行是字符串A,第2行是字符串B。
輸出:字符串A和B的編輯距離d(A,B)
思路:動態規划
開一個二維數組d[i][j]來記錄a0-ai與b0-bj之間的編輯距離,要遞推時,需要考慮對其中一個字符串的刪除操作、插入操作和替換操作分別花費的開銷,從中找出一個最小的開銷即為所求
具體算法:
首先給定第一行和第一列,然后,每個值d[i,j]這樣計算:d[i][j] = min(d[i-1][j]+1,d[i][j-1]+1,d[i-1][j-1]+(s1[i] == s2[j]?0:1));
最后一行,最后一列的那個值就是最小編輯距離
#include <stdio.h> #include <string.h> char s1[1000],s2[1000]; int min(int a,int b,int c) { int t = a < b ? a : b; return t < c ? t : c; } void editDistance(int len1,int len2) { int** d=new int*[len1+1]; for(int k=0;k<=len1;k++) d[k]=new int[len2+1]; int i,j; for(i = 0;i <= len1;i++) d[i][0] = i; for(j = 0;j <= len2;j++) d[0][j] = j; for(i = 1;i <= len1;i++) for(j = 1;j <= len2;j++) { int cost = s1[i] == s2[j] ? 0 : 1; int deletion = d[i-1][j] + 1; int insertion = d[i][j-1] + 1; int substitution = d[i-1][j-1] + cost; d[i][j] = min(deletion,insertion,substitution); } printf("%d\n",d[len1][len2]); for(int k=0;i<=len1;k++) delete[] d[k]; delete[] d; } int main() { while(scanf("%s %s",s1,s2) != EOF) editDistance(strlen(s1),strlen(s2)); }