樹形DP
樹形 DP,即在樹上進行的 DP。由於樹固有的遞歸性質,樹形 DP 一般都是遞歸進行的。
以下面這道題爲例,介紹一下樹形 DP 的一般過程。
https://www.luogu.org/problemnew/show/P1352
我們可以定義 dp[i][0/1] 代表以 i 爲根的子樹的最優解(第二維的值爲 0 代表 i 不參加舞會的情況,1 代表 i 參加舞會的情況)。
顯然,我們可以推出下面兩個狀態轉移方程(其中下面的 x 都是 i 的兒子):
dp[i][0] += max{dp[x][1],dp[x][0])(上司不參加舞會時,下屬可以參加,也可以不參加)
dp[i][1] += dp[x][0](上司參加舞會時,下屬都不會參加)
這裏我們定義一個son的vector數組,來存儲職員之間的關係,其中第一維存儲的是上司,第二維大小是可變的,存儲其對應的下屬。h[i]表示職員 i 快樂值,v[i[表示職員 i 是否有上司。
代碼如下:
//樹狀DP
#include<bits/stdc++.h>
# define LOCAL
using namespace std;
const int maxn = 6005;
int h[maxn],v[maxn];//h數組存儲快樂值,v數組存儲是否有boss
int dp[maxn][2];
vector<int> son[maxn];
void solve(int x){
dp[x][0] = 0;
dp[x][1] = h[x];
for(int i=0;i<son[x].size();i++){
int y = son[x][i];
solve(y);
dp[x][0] += max(dp[y][0],dp[y][1]);
dp[x][1] += dp[y][0];
}
}
int main(){
#ifdef LOCAL
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
#endif
int n;
cin >> n;
for(int i=1;i<=n;i++)
cin >> h[i];
for(int i=1;i<=n;i++){
int x,y;
cin >> x >> y; //輸入的數據最小爲1
if(!x && !y)
break;
son[y].push_back(x);
v[x] = 1;
}
int root;
for(int i=1;i<=n;i++)
if(!v[i]){
root = i;
break;
}
solve(root);
cout << max(dp[root][0],dp[root][1]) << endl;
return 0;
}
區間DP
模板: 階段 ····> 狀態 ····> 決策(從外到內)
//石子合併
#include<bits/stdc++.h>
# define LOCAL
const int inf = 0x3f3f3f;
const int maxn = 205;
using namespace std;
int a[maxn];//堆石子數
int sum[maxn];//計算某區間內得分
int dpmin[maxn][maxn],dpmax[maxn][maxn];//區間DP
void deal(int x){
memset(sum,0,sizeof(sum));
memset(dpmin,inf,sizeof(dpmin));
memset(dpmax,-inf,sizeof(dpmax));
for(int i=1;i<=x*2;i++){
dpmin[i][i] = 0;
dpmax[i][i] = 0;
sum[i] = sum[i-1] + a[i];
// cout << sum[i] << "\n";
}
}
void solve(int x){
for(int len=2;len<=x;len++)//階段
for(int l=1;l<=2*x-len+1;l++){//狀態:左端點
int r = len + l - 1;//狀態:右端點
for(int k=l;k<r && k<2*x-1;k++){
dpmin[l][r] = min(dpmin[l][r],dpmin[l][k]+dpmin[k+1][r] + sum[r]-sum[l-1]);
dpmax[l][r] = max(dpmax[l][r],dpmax[l][k]+dpmax[k+1][r] + sum[r]-sum[l-1]);
}
}
int ans1=inf,ans2=-inf;
for(int i=1;i<=x;i++){
ans1 = min(dpmin[i][i+x-1],ans1);
ans2 = max(dpmax[i][i+x-1],ans2);
}
cout << ans1 << "\n" << ans2 << endl;
}
int main(){
#ifdef LOCAL
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
#endif
int n;
while(cin >> n){
for(int i=1;i<=n;i++){
cin >> a[i];
a[i+n] = a[i];
}
deal(n);
solve(n);
cout << inf;
}
return 0;
}
注意是環狀的,需要和線性的區分:
memset(dp,0x3f3f3f3f,sizeof(dp));
memset(sum,0,sizeof(sum));
for(int i=1;i<=n;i++){
dp[i][i] = 0;
sum[i] = sum[i-1] + a[i];
}
for(int len=2;len<=n;len++)
for(int l=1;l<=n-len+1;l++){
int r = l+len-1;
for(int k=l;j<r;j++)
dp[l][r] = min(dp[l][r],dp[l][k]+dp[k+1][r]);
dp[l][r] += sum[r]-sum[l-1];
}
在數據處理上,環狀DP需要處理大於n(元素個數)的元素,使得構成一個環。
線性的dp數組每一個元素是隻會進行一次比較賦值;
環狀的dp數組的大於n(元素個數)元素是會進行第二次比較,根據要求取最小或是最大。