1.動(dòng)態(tài)規(guī)劃
一個(gè)問(wèn)題如果具有重復(fù)子問(wèn)題,那么可以用動(dòng)態(tài)規(guī)劃求解绪励,從而減少大量重復(fù)計(jì)算。
2.數(shù)塔問(wèn)題
問(wèn):從第一層走到最后一層所有路徑上的數(shù)字相加后最大和是多少却盘?
令dp[i][j]表示第i層第j個(gè)數(shù)字當(dāng)前的最大和拴疤,則狀態(tài)轉(zhuǎn)移方程:
邊界:,使用choice[i][j]數(shù)組記錄每一個(gè)節(jié)點(diǎn)是由下層哪個(gè)節(jié)點(diǎn)得到的字管,從而回溯構(gòu)造結(jié)果
程序代碼:
#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn = 100;
int numTower[maxn][maxn];
int dp[maxn][maxn];
int choice[maxn][maxn] = {0};
int main()
{
int n;
scanf("%d",&n); //2?êy
for(int i=0;i<n;i++)
{
for(int j=0;j<=i;j++)
{
scanf("%d",&numTower[i][j]);
}
}
//±???£o×?oóò?2?μ?×?ó??μμèóúμ±?°êy
for(int i=0;i<n;i++)
dp[n-1][i] = numTower[n-1][i];
//óé???áé?£?×′ì?×aò?·?3ì£o dp[i][j] = maxn(dp[i+1][j] + dp[i+1][j+1]) + numTower[i][j]
for(int i=n-2;i>=0;i--)
for(int j=0;j<=i;j++){
if(dp[i+1][j] >= dp[i+1][j+1]){
dp[i][j] = dp[i+1][j] + numTower[i][j];
choice[i][j] = 1;
}
else{
dp[i][j] = dp[i+1][j+1] + numTower[i][j];
choice[i][j] = 2;
}
}
printf("%d\n",dp[0][0]);
int j = 0;
for (int i = 0; i < n; ++i)
{
printf("%d",numTower[i][j] );
if (choice[i][j] == 2)
{
j++;
}
if(i != n-1)
printf(" ");
}
}
3.最大連續(xù)子序列和
問(wèn)題:給定K個(gè)整數(shù)的序列{ N1, N2, ..., NK }啰挪,其任意連續(xù)子序列可表示為{ Ni, Ni+1, ..., Nj },其中 1 <= i <= j <= K嘲叔。最大連續(xù)子序列是所有連續(xù)子序列中元素和最大的一個(gè)亡呵,例如給定序列{ -2, 11, -4, 13, -5, -2 },其最大連續(xù)子序列為{ 11, -4, 13 }借跪,最大和為20≌海現(xiàn)在增加一個(gè)要求,即還需要輸出該子序列的第一個(gè)和最后一個(gè)元素。
樣例輸入
5
-3 9 -2 5 -4
3
-2 -3 -1
0
樣例輸出
12 9 5
0 -2 -1
令dp[i]表示以i結(jié)尾的數(shù)字當(dāng)前最大和歇由,狀態(tài)轉(zhuǎn)移方程:
邊界:卵牍。
程序代碼:
#include <cstdio>
#include <cstring>
#include <cmath>
using namespace std;
const int maxn = 10010;
int a[maxn];
int dp[maxn];
int choice[maxn];
int main(int argc, char const *argv[])
{
int k;
while((scanf("%d",&k)) && k){
for(int i=0;i<k;i++)
scanf("%d",&a[i]);
dp[0] = a[0];
choice[0] = 1;
int maxInd = 0;
for(int i=1;i<k;i++){
if(a[i] >= dp[i - 1] + a[i]){
dp[i] = a[i];
choice[i] = 1;
}else{
dp[i] = dp[i-1] + a[i];
choice[i] = 2;
}
if(dp[i] > dp[maxInd])
maxInd = i;
}
if(dp[maxInd] < 0){
printf("%d %d %d\n",0,a[0],a[k-1] );
}else{
int p = maxInd;
while(choice[p] == 2)
p--;
printf("%d %d %d\n",dp[maxInd],a[p],a[maxInd] );
}
}
return 0;
}
4.最長(zhǎng)不下降子序列(LIS)
問(wèn)題:在一個(gè)數(shù)字序列中,找到一個(gè)最長(zhǎng)的子序列(可以不連續(xù))沦泌,使得這個(gè)子序列是不下降的糊昙,例如現(xiàn)有序列A={1,2,3,-1,-2,7,9},其最長(zhǎng)不下降子序列是{1,2,3,7,9}谢谦,長(zhǎng)度為5释牺。
令dp[i]表示以i結(jié)尾的序列當(dāng)前最長(zhǎng)不下降子序列的長(zhǎng)度,則狀態(tài)轉(zhuǎn)移分為兩種情況:
- 如果存在A[i]之前的元素Aj回挽,使得且没咙,則A[i]可跟在A[j]后形成更長(zhǎng)的序列
- 如果不存在,那么A[i]只能自己形成一條LIS千劈,長(zhǎng)度為1
狀態(tài)轉(zhuǎn)移方程:
初始狀態(tài):
程序代碼:
#include <cstdio>
#include <cstring>
#include <cmath>
using namespace std;
const int maxn = 10010;
int a[maxn];
int dp[maxn];
int choice[maxn];
int main(int argc, char const *argv[])
{
int k;
while((scanf("%d",&k)) && k){
for(int i=0;i<k;i++)
scanf("%d",&a[i]);
dp[0] = a[0];
choice[0] = 1;
int maxInd = 0;
for(int i=1;i<k;i++){
if(a[i] >= dp[i - 1] + a[i]){
dp[i] = a[i];
choice[i] = 1;
}else{
dp[i] = dp[i-1] + a[i];
choice[i] = 2;
}
if(dp[i] > dp[maxInd])
maxInd = i;
}
if(dp[maxInd] < 0){
printf("%d %d %d\n",0,a[0],a[k-1] );
}else{
int p = maxInd;
while(choice[p] == 2)
p--;
printf("%d %d %d\n",dp[maxInd],a[p],a[maxInd] );
}
}
return 0;
}
5.最長(zhǎng)公共子序列(LCS)
給定兩字符串A祭刚、B,如sadstory和adminsorry墙牌,其最長(zhǎng)公共子序列為adsory涡驮。
令dp[i][j]為A串的第i號(hào)字符和B串的第j號(hào)字符之前的LCS長(zhǎng)度,狀態(tài)轉(zhuǎn)移有以下兩種情況:
1.如果,則
2.否則,
狀態(tài)轉(zhuǎn)移方程:
邊界:
注:使用回溯法可求得具體的公共子串(待補(bǔ))
程序代碼:
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn = 1000;
char A[maxn],B[maxn];
int dp[maxn][maxn];
int main()
{
gets(A + 1); gets(B + 1);
int lenA = strlen(A + 1);
int lenB = strlen(B + 1);
for(int i=0;i<= lenA;i++)
dp[i][0] = 0;
for(int i=0;i<= lenB;i++)
dp[0][i] = 0;
//dp[i][j] = dp[i-1][j-1] + 1,A[i] = A[j]
//dp[i][j] = max(dp[i-1][j],dp[i][j-1])
for(int i=1;i<= lenA;i++)
for(int j=1;j<= lenB;j++)
if(A[i] == B[j])
dp[i][j] = dp[i-1][j-1] + 1;
else
dp[i][j] = max(dp[i-1][j],dp[i][j-1]);
printf("%d\n",dp[lenA][lenB]);
}
6.最長(zhǎng)回文子串
問(wèn)題:給定一字符串S喜滨,求其最長(zhǎng)回文子串長(zhǎng)度
如PATZJUJZTACCBCC,最長(zhǎng)回文子串為ATZJUJZTA捉捅,長(zhǎng)度為9
令dp[i][j]表示區(qū)間為[i,j]串是否是回文串,狀態(tài)轉(zhuǎn)移分為以下兩種情況:
1.若,那么如果區(qū)間[i+1,j-1]內(nèi)的串仍為回文串的話虽风,那么區(qū)間為[i,j]的串即為回文串
2.若棒口,那么區(qū)間為[i,j]的串肯定不是回文串
狀態(tài)轉(zhuǎn)移方程:
邊界:
注意:枚舉方法
如果按照i,j從小到大的順序枚舉焰情,無(wú)法保證dp[i+1][j-1]被計(jì)算過(guò)陌凳,因?yàn)橹怀跏蓟碎L(zhǎng)度為1和2的串,因此可以按串長(zhǎng)度即區(qū)間長(zhǎng)度進(jìn)行枚舉内舟。
程序代碼:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int const maxn = 1000;
char A[maxn];
int dp[maxn][maxn];
int main()
{
gets(A);
int len = strlen(A);
for(int i=0;i<len;i++)
{
dp[i][i] = 1;
if(i != len - 1 && A[i] == A[i+1])
dp[i][i+1] = 1;
ans = 2;
}
int ans = 1;
for(int L = 3;L<=len;L++)
{
for(int i=0;i+L-1<len;i++)
{
int j = i+L-1;
if(A[i] != A[j])
dp[i][j] = 0;
else
{
if(dp[i+1][j-1] == 1)
{
dp[i][j] = 1;
ans = L;
}
else dp[i][j] = 0;
}
}
}
printf("%d\n",ans);
}
7.DAG最長(zhǎng)路(待)
8.0-1背包問(wèn)題
問(wèn)題描述:有n件物品合敦,每件物品的重量為w[i],價(jià)值為c[i]⊙橛危現(xiàn)有一個(gè)容量為V的背包充岛,問(wèn)如何選取物品放入背包,使得背包內(nèi)物品的總價(jià)值最大耕蝉,其中每種物品只有一件崔梗。
令dp[i][v]表示前i種物品恰好能放入重量為v的背包所能得到的總價(jià)值,狀態(tài)轉(zhuǎn)移有以下兩種情況:
1.選擇第i種物品垒在,那么問(wèn)題轉(zhuǎn)化為前i-1種物品能夠放入重量為v-w[i]的背包所能獲得的最大價(jià)值蒜魄。
2.不選擇第i種物品,那么當(dāng)前最大價(jià)值即等于前i-1種物品能夠放入重量為v的背包所能獲得的最大價(jià)值。
狀態(tài)轉(zhuǎn)移方程:
邊界狀態(tài):
空間復(fù)雜度的優(yōu)化:
注意到dp[i][j]的計(jì)算只是依賴于i-1階段的V個(gè)狀態(tài)谈为,而不依賴于i-1之前的狀態(tài)旅挤,因此可以通過(guò)滾動(dòng)數(shù)組策略來(lái)去掉物品的維度,狀態(tài)方程:
當(dāng)前第i階段只依賴i-1階段的值伞鲫,因此在枚舉重量v的時(shí)候只能逆序枚舉粘茄,否則會(huì)當(dāng)前i階段的值會(huì)覆蓋i-1階段的值(后面枚舉需要用到)。
程序代碼:
#include <cstdio>
#include <algorithm>
#include <cmath>
using namespace std;
const int maxn = 100; //物品最大數(shù)
const int maxv = 1000; //背包最大重量
//每件物品的重量秕脓,每件物品的價(jià)值柒瓣,重量為v的背包所能裝物品的最大價(jià)值
int w[maxn], c[maxn], dp[maxv];
int selected[maxn]; //記錄每個(gè)物品是否被選擇
int main(int argc, char const *argv[])
{
int n,V;
scanf("%d%d",&n,&V);
for(int i=0;i<n;i++)
scanf("%d",&w[i]);
for(int i=0;i<n;i++)
scanf("%d",&c[i]);
//初始邊界,當(dāng)i=0時(shí)吠架,dp[v]=0
for(int v=0;v<=V;v++)
dp[v] = 0;
//注意點(diǎn):
//1. 滾動(dòng)數(shù)組來(lái)優(yōu)化空間:每次更新時(shí)用到的dp是i-1時(shí)刻的dp值芙贫,更新后變?yōu)閕時(shí)刻的dp值
//2. 狀態(tài)轉(zhuǎn)移方程: dp[v] = max(dp[v],dp[v - w[i]] + c[i])
//3. 逆序更新:保證i-1時(shí)刻的值不被覆蓋
//4. dp[v]此時(shí)保存的是重量為v恰好裝入物品的最大價(jià)值
//5. 階段狀態(tài)思想:本階段的所有狀態(tài)可有上一階段的所有狀態(tài)推理可得
// 階段:可選的不同物品,狀態(tài):在給定物品下的不同重量
//6. 無(wú)后效性:第i階段的所有狀態(tài)取值僅取決于i-1階段的狀態(tài)取值
// 滿足無(wú)后效性的問(wèn)題可用滾動(dòng)數(shù)組來(lái)求解诵肛,不滿足的可以增加維度
for(int i=1;i<=n;i++){
for(int v = V;v>=w[i];v--){
dp[v] = max(dp[v],dp[v - w[i]] + c[i]);
}
}
//尋找最大的dp[v]
int k = 0;
for(int v=0;v<=V;v++){
if(dp[v] > dp[k])
k = v;
}
printf("%d\n", dp[k]);
return 0;
}
9.完全背包問(wèn)題
問(wèn)題描述:有n種物品屹培,每種物品的單件重量為w[i],價(jià)值為c[i]≌荩現(xiàn)有一個(gè)容量為V的背包,問(wèn)如何選取物品放入背包蓄诽,使得背包內(nèi)物品的總價(jià)值最大薛训,其中每種物品都有無(wú)窮件。
令dp[i][v]表示前i件物品恰好放入容量為v的背包中所能獲得的最大價(jià)值仑氛,和0-1背包的區(qū)別在于:
1.不放第i件物品乙埃,那么,同0-1背包
2.放第i件物品锯岖,那么介袜,這里dp[i][v]依賴階段i的v-w[i]的狀態(tài),因?yàn)槲锲窋?shù)量無(wú)限出吹,所以可以重復(fù)的放物品i
狀態(tài)轉(zhuǎn)移方程:
邊界狀態(tài):
同樣遇伞,可以用滾動(dòng)數(shù)組來(lái)去掉物品維度,此時(shí)需要正序枚舉背包容量捶牢,因?yàn)殡A段i的值依賴當(dāng)前階段i鸠珠,需要使用覆蓋的數(shù)據(jù),狀態(tài)轉(zhuǎn)移方程:
10.完全背包可行方案數(shù)的問(wèn)題
問(wèn)題描述:傳統(tǒng)地秋麸,一個(gè)貨幣系統(tǒng)是由1,5,10,20 或 25,50, 和 100的單位面值組成的渐排,現(xiàn)用這些硬幣支付面值為A的商品,問(wèn)有多少種不同的方案灸蟆,輸出字典順序最小的方案驯耻,這些硬幣無(wú)窮多個(gè)。
問(wèn)題特點(diǎn):相當(dāng)于求下列方程解的個(gè)數(shù):
其中表示n個(gè)硬幣的面值,表示組合方案可缚。
令dp[i][v]表示前i個(gè)硬幣恰能組成面值為v的方案數(shù)霎迫,則狀態(tài)轉(zhuǎn)移方程:
初始化:
使用滾動(dòng)數(shù)組,狀態(tài)轉(zhuǎn)移方程可轉(zhuǎn)化為:
進(jìn)行正序枚舉面值v即可
程序代碼:
#include <cstdio>
#include <algorithm>
#include <vector>
using namespace std;
/*
1.此類問(wèn)題為完全背包的可行解個(gè)數(shù)問(wèn)題城看,需要掌握
2.令dp[i][v]表示前i個(gè)物品恰能裝滿重量為w的方案數(shù)
3.狀態(tài)轉(zhuǎn)移方程:dp[i][v] = dp[i-1][v] + dp[i][v-w[i]]女气,dp[:][0] = 1
4.討論:第一種狀態(tài)轉(zhuǎn)移:不選物品i,狀態(tài)轉(zhuǎn)移取決于i-1階段
第二種狀態(tài)轉(zhuǎn)移:選擇物品i测柠,由于物品i的個(gè)數(shù)不受限制炼鞠,因此
狀態(tài)轉(zhuǎn)移取決于第i階段,可以選擇多個(gè)物品i,但會(huì)受到容量v的限制
5.可以將上述二維狀態(tài)轉(zhuǎn)移方程利用滾動(dòng)數(shù)組變成一維
6.進(jìn)行正序dp
*/
const int maxn = 30;
const int maxm = 10010;
long long dp[maxm],w[maxn];
int main(int argc, char const *argv[])
{
int n,V;
scanf("%d%d",&n,&V);
for(int i=1;i<=n;i++)
scanf("%lld",&w[i]);
//初始化
dp[0] = 1;
for(int i=1;i<=V;i++)
dp[i] = 0;
for(int i=1;i<=n;i++){
for(int v=w[i];v<=V;v++)
dp[v] = dp[v] + dp[v-w[i]];
}
printf("%lld\n",dp[V] );
return 0;
}
11.非完全背包問(wèn)題
問(wèn)題描述:有1元轰胁、5元谒主、50元、100元赃阀、500元的硬幣各1霎肯、5、10榛斯、50观游、100、500枚⊥运祝現(xiàn)在要用這些硬幣來(lái)支付A元懂缕,有多少種支付方案。并且輸出字典順序最小的方案王凑。