Timus 1018 树形DP

本文介绍了一种使用树形动态规划方法解决特定问题的技术。该问题旨在找到一棵树中通过删除一定数量的边来最大化保留苹果数的最佳方案。文章详细解释了状态定义、转移方程,并给出了完整的C++代码实现。

树形DP,令res[i][j]表示以i为根的子树删除j条边所保留的最大苹果数;

res[i][j]=max(res[i][j],res[lChild][k]+res[rChild][j-k]+value[i]);(0<=j<=inum[i],0<=k<=inum[lChild])

inum[i]表示以i为根的子树树枝的个数,value[i]以i为端点的树枝的苹果树。

代码如下:

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
#define MAX 123456789
struct node
{
int value,lChild,rChild;
};
node nVert[101];
struct Adta
{
int i,value,next;
};
Adta mVert[202];
int res[101][101],head[101],visit[101],inum[101],N,n,m;
int add(int s,int t,int w)
{
mVert[N].i=t;
mVert[N].value=w;
mVert[N].next=head[s];
head[s]=N;
return N++;
}
int init(int i)//利用构造好的临界表重新建树
{
int j;
visit[i]=1;
nVert[i].lChild=nVert[i].rChild=0;
for(j=head[i];j;j=mVert[j].next)
if(!visit[mVert[j].i])
{
if(nVert[i].lChild==0)
{
nVert[i].lChild=mVert[j].i;
nVert[mVert[j].i].value=mVert[j].value;
init(mVert[j].i);
}
else if(nVert[i].rChild==0)
{
nVert[i].rChild=mVert[j].i;
nVert[mVert[j].i].value=mVert[j].value;
init(mVert[j].i);
}
}
return 1;
}
int max(int a,int b)
{
return a > b ? a : b;
}
int dfs(int i)
{
int j,lc,rc,l;
lc=nVert[i].lChild;
rc=nVert[i].rChild;
if(!lc && !lc)
{
res[i][0]=nVert[i].value;
return res[i][1];
}
if(lc) dfs(lc);
if(rc) dfs(rc);
for(j=0;j<n-m && j<=inum[i];j++)
{
for(l=0;l<=j;l++)
if(l<=inum[lc]+1 && j-l<=inum[rc]+1) res[i][j]=max(res[i][j],res[lc][l]+res[rc][j-l]+nVert[i].value);
}
return res[i][n-m-1];
}
int GetInum(int i)//获取以i为根的子树的树枝数
{
if(!nVert[i].lChild && !nVert[i].rChild)
{
return 0;
}
if(nVert[i].lChild)
inum[i]+=GetInum(nVert[i].lChild)+1;
if(nVert[i].rChild)
inum[i]+=GetInum(nVert[i].rChild)+1;
return inum[i];
}
int main()
{
int i,s,t,w;
while(scanf("%d %d",&n,&m)!=EOF)
{
N=1;
memset(head,0,sizeof(head));
memset(visit,0,sizeof(visit));
memset(res,0,sizeof(res));
memset(inum,0,sizeof(inum));
for(i=1;i<n;i++)
{
scanf("%d %d %d",&s,&t,&w);
head[s]=add(s,t,w);
head[t]=add(t,s,w);
}
inum[0]=-1;
init(1);
GetInum(1);
printf("%d\n",dfs(1));
}
return 0;
}



转载于:https://www.cnblogs.com/yu-chao/archive/2012/04/06/2434642.html

<think>我们正在讨论动态规划中的斜率优化技术。斜率优化主要用于优化形如 $dp[i] = \min_{j < i}\{ dp[j] + f(i, j) \}$ 的转移方程,其中 $f(i, j)$ 可以分解为关于 $i$ 和 $j$ 的函数的组合。斜率优化的核心思想是将转移方程转化为直线方程,利用决策点的斜率单调性,通过维护一个凸包(上凸包或下凸包)来快速找到最优决策点。 根据引用内容,斜率优化通常适用于以下情况: 1. 转移方程可以写成:$dp[i] = \min_{j < i} \{ dp[j] + A(i) \cdot B(j) + C(i) + D(j) \}$,其中 $A(i)$ 和 $B(j)$ 是分别只与 $i$ 和 $j$ 有关的函数。 2. 通过代数变形,将转移方程转化为:$dp[j] + D(j) = -A(i) \cdot B(j) + (dp[i] - C(i))$。这样,我们可以将每个决策点 $j$ 看作二维平面上的一条斜率为 $-A(i)$ 的直线,截距为 $dp[i]-C(i)$,而每个决策点 $j$ 对应于点 $(B(j), dp[j]+D(j))$。 3. 当 $A(i)$ 具有单调性(例如递增)时,我们可以利用单调队列维护凸包;当不具有单调性时,可能需要二分查找。 具体步骤: 1. **变形方程**:将转移方程变形,使得只与 $j$ 相关的项($dp[j]+D(j)$ 和 $B(j)$)作为点的坐标,而只与 $i$ 相关的项($A(i)$)作为斜率。 2. **维护凸包**:根据斜率的变化趋势(递增或递减)维护一个凸包(上凸或下凸)。通常,如果斜率是递增的,则维护下凸包(最小值);如果斜率是递减的,则维护上凸包(最大值)。但具体问题需要具体分析。 3. **寻找最优决策点**:在凸包上,最优决策点满足它前后的点所连成的直线的斜率与当前斜率 $k_i$ 满足一定的关系(例如,对于下凸包,最优决策点满足:前一个点与当前点的斜率小于 $k_i$,而当前点与后一个点的斜率大于等于 $k_i$)。 4. **加入新决策点**:在计算完当前状态 $dp[i]$ 后,将点 $(B(i), dp[i]+D(i))$ 加入凸包,同时维护凸包性质(删除破坏凸性的点)。 引用[1]中给出了一个例子:凸包上的斜率呈单增态,那么最优决策点就是第一个斜率大于等于 $k_0$ 的线段的左端点。由于斜率单调递增,可以使用二分查找。 引用[4]提到,当右边是一个递增的变量(即斜率单调递增)时,会选用斜率递增的单调队列(上凸包或下凸包,具体问题具体分析)。在引用[4]的图中,斜率递增,因此维护的是上凸包?但注意:通常下凸包对应最小值问题,上凸包对应最大值问题。但具体问题需要分析。 下面我们通过一个经典问题“任务安排”来具体说明(参考引用[5]的问题): 问题描述:有 $n$ 个任务,每个任务需要 $t_i$ 的时间,费用系数为 $c_i$。任务可以分批,每批任务有一个启动时间 $S$。每批任务连续完成。设第 $i$ 个任务完成的时间为 $T_i$,则其费用为 $T_i \times c_i$。总费用为所有任务费用之和。要求最小化总费用。 状态定义:设 $dp[i]$ 表示完成前 $i$ 个任务的最小费用。 转移方程:考虑将 $j+1$ 到 $i$ 的任务分成一批,则: $$ dp[i] = \min_{0 \leq j < i} \left\{ dp[j] + \text{sumT}_i \times (\text{sumC}_i - \text{sumC}_j) + S \times (\text{sumC}_n - \text{sumC}_j) \right\} $$ 其中,$\text{sumT}_i = \sum_{k=1}^i t_k, \text{sumC}_i = \sum_{k=1}^i c_k$。 变形:将只与 $j$ 相关的项提出来: $$ dp[i] = \min_{j} \left\{ dp[j] - (S + \text{sumT}_i) \times \text{sumC}_j \right\} + \text{sumT}_i \times \text{sumC}_i + S \times \text{sumC}_n $$ 令 $A(i) = S + \text{sumT}_i$,$B(j) = \text{sumC}_j$,$C(i) = \text{sumT}_i \times \text{sumC}_i + S \times \text{sumC}_n$,$D(j) = 0$,则: $$ dp[i] = \min_{j} \left\{ dp[j] - A(i) \cdot B(j) \right\} + C(i) $$ 移项: $$ dp[j] = A(i) \cdot B(j) + (dp[i] - C(i)) $$ 这里,$A(i)$ 是递增的(因为 $\text{sumT}_i$ 递增),$B(j)$ 也是递增的($\text{sumC}_j$ 递增)。 因此,每个决策点 $j$ 对应于点 $(B(j), dp[j])$。我们要求的是斜率为 $A(i)$ 的直线过某个决策点 $j$ 时的截距($dp[i]-C(i)$)最小。所以,我们维护一个下凸包(因为要求最小值,且斜率 $A(i)$ 递增)。 维护凸包步骤: 1. 在队列中,相邻点之间的斜率递增(下凸包)。 2. 当计算 $dp[i]$ 时,因为斜率 $A(i)$ 递增,所以队首的决策点可能不再是最优(因为后面的点可能更优),我们需要检查队首两个点之间的斜率是否小于 $A(i)$,如果小于,则队首点不是最优,弹出队首,直到队首两点斜率大于等于 $A(i)$。此时队首点即为最优决策点。 3. 计算 $dp[i]$。 4. 将点 $(B(i), dp[i])$ 加入队尾。在加入之前,需要检查队尾三个点是否保持凸性(即队尾三个点中,中间点是否在下面,如果不是则弹出队尾点,直到满足凸性)。 具体实现: - 使用双端队列(deque)存储决策点的下标(或者直接存储坐标)。 - 队列中存储的决策点对应凸包上的点。 伪代码: ```python 初始化:dp[0] = 0, 队列q,将0加入队列 for i in range(1, n+1): # 从队首弹出斜率小于当前斜率A(i)的点(因为斜率递增,所以队首的点可能不再是最优) while len(q) >= 2 and slope(q[0], q[1]) <= A(i): pop_front(q) j = q.front() # 最优决策点 dp[i] = dp[j] - A(i)*B(j) + C(i) # 将点i加入队列,维护凸包 while len(q) >= 2 and slope(q[-1], i) <= slope(q[-2], q[-1]): pop_back(q) push_back(q, i) ``` 其中,`slope(j, k)` 表示点j和点k之间的斜率:$(dp[k]-dp[j]) / (B(k)-B(j))$。 注意:这里假设 $B(k) \neq B(j)$,且 $B$ 是递增的,所以分母不为0。 通过以上步骤,我们可以在 $O(n)$ 的时间复杂度内完成动态规划。 总结:斜率优化动态规划的关键在于将转移方程变形为直线方程,利用决策点的凸性,通过维护凸包来快速寻找最优决策点。 §§相关问题§§ 1. 斜率优化动态规划适用于哪些类型的转移方程? 2. 如何判断一个动态规划问题是否可以使用斜率优化? 3. 斜率优化中,如何维护凸包? 4. 斜率优化中,当斜率不单调时,如何寻找最优决策点? 5. 能否举例说明一个斜率优化动态规划的具体问题?</think>### 动态规划中的斜率优化技术 斜率优化是动态规划的高级优化技术,用于**寻找最优决策点**并降低时间复杂度。其核心思想是将状态转移方程转化为几何形式(直线斜率),通过维护**决策点的凸包**(上凸包或下凸包)快速定位最优解。以下是关键技术要点: --- #### 一、斜率优化的适用条件 1. **状态转移方程形式** 需满足:$dp[i] = \min_{j < i} \\{ dp[j] + f(i, j) \\}$ 其中 $f(i, j)$ 可拆分为:$f(i, j) = A(i) \cdot B(j) + C(i) + D(j)$ ($A(i), B(j)$ 分别为 $i$ 和 $j$ 的函数) 2. **斜率单调性** - 若 $A(i)$ 单调递增/递减,可用单调队列维护凸包($O(n)$) - 若 $A(i)$ 无单调性,需二分查找凸包($O(n \log n)$) --- #### 二、实现步骤(以最小化问题为例) 1. **变形状态转移方程** 将方程改写为: $$ \underbrace{dp[j] + D(j)}_{Y_j} = \underbrace{-A(i)}_{斜率} \cdot \underbrace{B(j)}_{X_j} + \underbrace{(dp[i] - C(i))}_{截距} $$ 此时每个决策点 $j$ 对应平面点 $(X_j, Y_j) = (B(j), dp[j] + D(j))$。 2. **维护凸包** - **下凸包**(最小值问题):相邻点斜率递增($\frac{Y_{k+1}-Y_k}{X_{k+1}-X_k} \geq \frac{Y_k-Y_{k-1}}{X_k-X_{k-1}}$) - **上凸包**(最大值问题):相邻点斜率递减 *示例凸包维护(引用[4]):* > 斜率递增的单调队列对应下凸包,通过删除队尾破坏凸性的点保证斜率单调性[^4]。 3. **寻找最优决策点** - **单调队列法**($A(i)$ 递增时): 1. 删除队首斜率 $\leq A(i)$ 的点(因其不会更优) 2. 队首即最优决策点 $j^*$ *示例(引用[1]):* > 当凸包斜率单增时,找到第一个满足 $\frac{Y_j-Y_{j-1}}{X_j-X_{j-1}} \geq A(i)$ 的点 $j$,其左端点即最优决策点[^1]。 - **二分法**($A(i)$ 无单调性): 在凸包上二分查找满足 $\frac{Y_j-Y_{j-1}}{X_j-X_{j-1}} \geq A(i)$ 的最小 $j$。 4. **更新状态与决策点** 计算 $dp[i] = dp[j^*] + A(i) \cdot B(j^*) + C(i)$,并将点 $(B(i), dp[i] + D(i))$ 加入凸包。 --- #### 三、经典问题示例 **任务安排问题**(引用[5]): - **状态转移**:$dp[i] = \min_{j < i} \\{ dp[j] + (sumT_i + S) \cdot (sumC_i - sumC_j) \\}$ - **斜率优化**: - $A(i) = sumT_i + S$, $B(j) = sumC_j$ - 维护点 $(sumC_j, dp[j])$ 的下凸包 - 单调队列维护最优 $j$(因 $sumT_i$ 递增) *优化效果*:时间复杂度从 $O(n^3)$ 降至 $O(n)$[^5]。 --- #### 四、注意事项 1. **避免除零错误**:若 $X_j$ 可能相等,需特判(如直接比较 $Y_j$)。 2. **凸包方向**:最小值问题用下凸包,最大值问题用上凸包。 3. **决策单调性**:若 $B(j)$ 非单调,需使用平衡树/CDQ分治维护凸包。 > 核心优势:将动态规划中枚举决策的复杂度从 $O(n)$ 降至 $O(1)$ 或 $O(\log n)$,适用于大规模问题[^2][^3]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值