树形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(元素个数)元素是会进行第二次比较,根据要求取最小或是最大。