動態規劃之樹形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(元素個數)元素是會進行第二次比較,根據要求取最小或是最大。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章