动态规划之树形DP,区间DP

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

发布了24 篇原创文章 · 获赞 39 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/Geek_/article/details/96477406
今日推荐