每做一次決策就可以得到解得一部分,當做完所有決策做完之後,完整的解就浮出水面了。在回溯法中,每次決策對應給一個結點產生新的子樹,而解的生成過程對應一顆解答樹,結點的層數就是“下個待填充位置”cur
9.3.1多段圖的最短路
多段圖是一種特殊的DAG,其結點可以劃分成若干個階段,每個階段只有上一個階段所決定
例題:單向TSP
分析:
在這個題目中,每一列就是一個階段,每個階段有三種決策
多階段決策的最優化問題往往可以通過用動態規劃解決,其中,狀態及其轉移類似於回溯法中的解答樹,解答樹的中的“層數”,也就是遞歸函數中的“當前填充位置cur”,描述的是即將完成的決策序號,在動態規劃中被稱爲階段
有了前面的經驗,不難設計出狀態,設d(i,j)爲從格子(i,j)出發到最後一列的最小開銷,但是本題不僅要輸出解,還要求字典序最小,這就需要在計算d(i,j)的同時記錄“下一列的行號”的最小值
int ans=INF,first=0;
for(int j=n-1;j>=0;j--){
for(int i=0;i<m;i++){
if(j==n-1)
d[i][j]=a[i][j]; //邊界
else{
int rows[3]={i,i-1,i+1};
if(i==0) rows[1]=m-1;
if(i==m-1) rows[2]=0;
sort(rows,rows+3); //重新排序,以便找到字典序最小
d[i][j]=INF;
for(int k=0;k<3;k++){
int v=d[rows[k]][j+1]+a[i][j];
if(v<d[i][j]) {d[i][j]=v;next[i][j]=rows[k]; }
}
}
if(j==0&&d[i][j]<ans)
{
ans=d[i][j];
first=i;
}
}
}
printf("%d",first+1); //輸出第一列
for(int i=next[first][0],j=1;j<n;i=next[i][j],j++)
printf(" %d",i+1);
printf("\n%d\n",ans);
代碼還有一定的問題
9.3.2 0-1揹包問題
下面是代碼,答案是d[1][C];
for(int i=n;i>=1;i--)
for(int j=0;j<=C;j++){
d[i][j]=(i==n?0:d[i+1][j]); //d[i][j]表示把第i,i+1,i+2,,n個物品裝到容量爲j的揹包的最大重量
if(j>=V[i])
d[i][j]=max(d[i][j],d[i+1][j-V[i]]+W[i]);
}
//答案是d[1][C];
還有另一種“對稱”的狀態定義:用f[i][j]表示“把前i個物品裝到容量爲j的揹包中的最大總重量”,其狀態轉移方程也不難得出
for(int i=1;i<=n;i++)
for(int j=0;j<=C;j++){
f[i][j]=(i==1?0:f[i-1][j]);
if(j>=V[i])
f[i][j]=max(f[i][j],f[i-1][j-V[i]]+W[i]);
}
看上去這兩種方式完全對稱的,但是其存在細微區別,新的方法可以邊讀入邊計算,而不必把V和W保存下來
for(int i=1;i<=n;i++){
scanf("%d%d",&V,&W);
for(int j=0;j<=C;j++){
f[i][j]=(i==1?0:f[i-1][j]);
if(j>=V)
f[i][j]=max(f[i][j],f[i-1][j-V]+W);
}
}