樹形dp暫時告一段落,今天總結了一下:
首先,樹形dp=樹+dp,是一種二維的dp題目,也可以結合揹包問題,因為子樹的數量不確定,所以一般用vector來儲存子節點或子樹個數。
平時作的動態規劃都是線性的或者是建立在圖上的,線性的動態規劃有二種方向既向前和向後,相應的線性的動態
規劃有二種方法既順推與逆推,而樹型動態規劃是建立在樹上的,所以也相應的有二個方向:
1、葉->根:在回溯的時候從葉子節點往上更新資訊
2、根 - >葉:往往是在從葉往根dfs一遍之後(相當於預處理),再重新往下獲取最後的答案。
先看一道簡單的例題:
poj 2342 anniversary party 先來個題入門一下~
題意:某公司要舉辦一次晚會,但是為了使得晚會的氣氛更加活躍,每個參加晚會的人都不希望在晚會中見到他的直接上司,現在已知每個人的活躍指數和上司關係(當然不可能存在環),求邀請哪些人(多少人)來能使得晚會的總活躍指數最大。
解題思路:
任何乙個點的取捨可以看作一種決策,那麼狀態就是在某個點取的時候或者不取的時候,以他為根的子樹能有的最大活躍總值。分別可以用f[i,1]和f[i,0]表示第i個人來和不來。
當i來的時候,dp[i][1] += dp[j][0];//j為i的下屬
當i不來的時候,dp[i][0] +=max(dp[j][1],dp[j][0]);//j為i的下屬
[cpp]view plain
copy
#include
#include
#include
#include
#include
#include
#include
#include
using
namespace
std;
#define maxn 6005
intn;
intdp[maxn][2],father[maxn];
//dp[i][0]0表示不去,dp[i][1]1表示去了
bool
visited[maxn];
void
tree_dp(
intnode)
} } int
main()
root = 0;//記錄父結點
bool
beg = 1;
while
(scanf(
"%d %d"
,&c,&f),c||f)
} while
(father[root])
//查詢父結點
root=father[root];
tree_dp(root);
intimax=max(dp[root][0],dp[root][1]);
printf("%d\n"
,imax);
} return
0;
}
這跟普通的dp一樣,只不過擴充套件到了二維,只要找到狀態轉移方程,再用遞迴逐個算出每個子節點所對應的權值,就ok啦!
接下來再看一道結合揹包問題的樹形dp:
0-1揹包裸**:
for i=1..n
for v=v..0
f[v]=max;
狀態轉移方程:f[root][k]=max(f[root][k],f[root][k-j]+dp[u][j]);
m是個數,j是存幾個,f[i][j]表示的是以i為根攻克j個城堡(且這j個城堡必須是它子樹上的,不包括它本身),dp[i][j]表示的是是以i為根攻克j個城堡(且這j個城堡必須是它子樹上的,一定它本身,ans[i]表示每個城堡的寶物,所以一定有dp[i][1]=ans[i];)。
for(int k=m;k>=0;k--)
for(int j=0;j<=k;j++)
f[root][k]=max(f[root][k],f[root][k-j]+dp[u][j]);
更新f[root][0~m]陣列,然後全部更新完之後更新dp[root][0~m]。
如圖所示樣例,先從root即0點訪問3,3沒有孩子,執行更新dp操作
,因為所以葉子都滿足dp[i][0~m]=ans[i],所以dp[3][0~m]都等於ans[3],以下同理。
返回到root,更新f[0][m~0]。
訪問root-->2-->7-->6,訪問到葉子,更新dp[6][0~m]。返回7,更新f[7][m~0],
從7-->5,更新葉子節點dp[5][0~m],
從5-->7,再次更新f[7][m~0],
從7-->2,更新dp[7][0~m],返回2節點,更新f[2][m~0],
從2-->4,更新葉子節點dp[4][0~m],
從4-->2,更新f[2][m~0],
從2-->1,更新dp[1][0~m],
從1-->2,更新f[2][m~0],
從2-->root,更新dp[2][0~m],
更新f[0][m~0],更新dp[0][0~m]。
[
#include
#include
#define n 205
intn,m,edgenum=0;
intans[n],dp[n][n],f[n][n];
intvisit[n],head[n];
struct
lineedge[n];
intmax(
inta,
intb)
void
add(
intu,
intv)
void
dfs(
introot)
} for(
inti=1;i<=m+1;i++)
dp[root][i]=f[root][i-1]+ans[root];
} int
main()
dfs(0);
printf("%d\n"
,dp[0][m+1]);
} }
以上只是入門題目,能較簡單的反映樹形dp的基本思想,更複雜的題目說實話能看懂題解就不錯了,現在根本做不出來。。。
還需努力!
週中訓練筆記
rand 函式 rand n 範圍 0 n 1 n rand m n 1 範圍n m 線段樹的概括 1.是乙個完全二叉樹 2.主要用於解決解決連續區間的動態查詢問題 現在對於線段樹的認識還很侷限,所以概括的也非常籠統,接下來的幾天會繼續研究線段樹的。然後就是今天的廣西邀請賽重現賽的題 今天因為下午有...
週中訓練筆記(四)
線段樹總結 線段樹儲存的是區間的資訊然後在可以進行區間的各種操作。對於節點a n 它的左兒子為a 2 n 右兒子為a 2 n 1 假如這個節點所表示的區間為 1,5 那麼它左兒子表示的區間為 1,3 右兒子表示的區間為 4,5 公式為mid l r 2,左兒子表示的區間為 l,mid 右兒子表示的區...
週中訓練筆記11 線段樹總結
線段樹專題接近尾聲了,是時候總結一波了,說來慚愧,線段樹專題差不多都是參照題解才做出來的,雖然知道是套模板但是具體細節真的很拿人啊。主要總結一下線段樹的模板吧,題目的 實現都要以模板為框架構造 首先提出乙個問題 給你n個數,有兩種操作 1 給第i個數的值增加x 2 詢問區間 a,b 的總和是什麼?輸...