@

前言

因为树的形式非常适合递归,他所带来的访问顺序也是非常符合拓扑排序的,故而在处理子树类问题时,dp可以很好的利用相邻层级之间的关系和逻辑,非常符合dp的“口味”,所以我们才有了这个树形dp。

树形dp和线性dp没有什么本质上的区别,只不过一个是在树上,一个是在线上,唯一的一个不同点就是树形dp可以大致的定形,而线性dp却不可以。

树形dp的转移方式

一般情况下的树形dp只会有两种方式,要么从上到下(父亲到儿子),要么从下到上(儿子到父亲),一般情况下从下到上的可能性更多,因为儿子更多,可选性也更大,自然答案也更多,出题人也更愿意考。那么我们在判断出来一道题是树形dp的时候,我们就要主要去关注父亲和儿子之间的关系了,也就是说要关注相邻层级的关系,这也就是我所说的可以大致定形。但是这些都建立在一个前提之下——我们知道这个题要用树形dp。

树形dp的使用的场景

我们一般在涉及树形结构的最优解的时候,会使用树形dp。我在这里大致总结几个,但是不一定是全部的,还是要自己总结才行。

  • 涉及树形的最优策略的情况(最大值,最小值,最优方案等),并且答案可以从已知的子树中转移或合并得到,那么这个就非常适合树形dp来做。下面是一些例子。

    • 树的直径:求最长路径,可以通过子树的最长路径来计算。
    • 最大独立集:选出最多不相邻的节点,当前点选或不选的情况依赖于子树的选择情况。
  • 涉及子树之间要传递信息或相互转移的情况,并且对于一个节点而言的最优解会依赖于其子树的计算结果的时候,可以考虑使用树形dp,同时这里也可以简单的定个型:这里一般都是采用后序DP,也就是说从下而上的计算。下面是一些例子。

    • 树上背包问题:使得总价值最大但是会受到某些条件的限制,类似于01背包。
    • 树上最小支配集:要求覆盖整棵树的最小节点集,他的状态也依赖于子树的选或不选。
  • 涉及从根或父亲节点向子节点进行转移的时候,同时答案也是在子节点的上面的时候,也可以考虑树形dp,并且一般情况下,这个都是采用先序DP,也就说从上而下的计算。下面是一些例子。

    • 重复计算树的每个值(比如说子树):当以不同点为根的时候,如何快速记录出最短路径或子树和。
    • 换根dp(Re-rooting DP):当以不同点为根节点的时候,所要求的值,比如说高度和。
  • 当需要避免重复计算来提高效率的时候可以采用树形dp的方式来优化,而优化方式也就是记忆化递推,这两种非常经典的方法。下面是一些例子。

    • 树上路径问题:求树上一个点到其他所有点的最短路径的时候,我们也可以考虑树形dp来转移。
    • 二叉树的最优构造方案:当只给你一棵树的中序遍历和一些限制条件的时候,要求你求出符合条件的树有多少种,我们也可以考虑树形dp来解决。

小结

我们可以在拿到树的问题的时候,问自己以下几个问题,以便来让我们判断改用什么方法来做。

  1. 树上的某个值能不能通过子树的值来计算? \(\to\) 树形dp
  2. 问题的最优解是否需要子树的答案来合并? \(\to\) 树形dp(后序)
  3. 是否可以通过递归的方式,把大问题分解成小问题? \(\to\) 树形dp(先序)
  4. 有没有大量的计算?\(\to\) 树形dp(优化)

初步感知——简单的树形dp

例题1

洛谷——最大子树和

这道题是要求一个为根所组成的树的最大值,这个问题的最优解需要子树的答案来合并,所以我们采用树形dp。

顺着他的题意,我们不妨设 \(dp[i]\) 表示以 \(i\) 为根节点的树的最大值,因为这个点的答案依赖于他儿子的答案,所以我们很容易得到这个状态转移方程:

\[dp[i]=\sum_{j \in son[i]}^{j \neq fa_i}\max(dp[j],0)
\]

但是这样的方式我们只求了当前点关于他的孩子所得到的答案,他自己还没有算,所以我们还要调整一下。

\[dp[i]=\max(dp[i]+num[i],0)
\]

通过上下两个状态转移方程,我们就可以很轻松的得到答案。

#include<bits/stdc++.h>
using namespace std;
const int INF=1e5+10;
int num[INF],dp[INF],ans=INT_MIN;
vector<int> mp[INF];
void dfs(int x,int fa){
int len=mp[x].size();
for (int i=0;i<len;i++){
if (mp[x][i]==fa)continue;
int t=mp[x][i];
dfs(t,x);
dp[x]+=max(dp[t],0);
}
dp[x]=max(0,dp[x]+num[x]);
ans=max(dp[x],ans);
}
int main(){
int n;
cin>>n;
for (int i=1;i<=n;i++){
cin>>num[i];
ans=max(ans,num[i]);
}
if (ans<0){
cout<<ans;
return 0;
}
for (int i=1;i<n;i++){
int u,v;
cin>>u>>v;
mp[u].push_back(v);
mp[v].push_back(u);
}
dfs(1,-1);
cout<<ans;
return 0;
}

例题2

洛谷——时态同步

这个问题的最优解需要子树的答案来合并,所以我们采用树形dp。

我们不妨设 \(dp[x]\) 表示x的孩子到达x的最大时间。那么我们就要关注一下父亲和孩子之间的关系,可以很轻松的得到以下这个状态转移方程。

\[dp[i]=\max{dp[j]+mp[i][j].w}~~(j\in mp[i])
\]

由此我们只需要在求得答案之后对每个儿子节点做差然后求和即可。

#include<bits/stdc++.h>
using namespace std;
const int INF=5e5+10;
struct Node{
long long point,num;
};
vector<Node> mp[INF];
long long ans,dp[INF];
void dfs(int x,int fa){
int len=mp[x].size();
for (int i=0;i<len;i++){
if (mp[x][i].point==fa)continue;
int t=mp[x][i].point;
dfs(t,x);
dp[x]=max(dp[x],dp[t]+mp[x][i].num);
}
for (int i=0;i<len;i++){
if (mp[x][i].point==fa)continue;
int t=mp[x][i].point;
ans+=dp[x]-dp[t]-mp[x][i].num;
}
}
int main(){
int n,st;
cin>>n>>st;
for (int i=1;i<n;i++){
int a,b,t;
cin>>a>>b>>t;
mp[a].push_back({b,t});
mp[b].push_back({a,t});
}
dfs(st,-1);
cout<<ans;
return 0;
}

深入分析——树形dp的经典模型

最大独立集

什么是最大独立集?

顾名思义,就是所选出来的点,两两之间没有直接联系,也就说没有直接的上下层级关系。我们就要在满足这个条件下找到可行的最大的方案。

而没有上司的舞会就是一道典型的此类题目,所以我们就以这道题来讲:

洛谷——没有上司的舞会

因为相邻两点不能同时存在,所以说应该我们只需要关注一下父亲和儿子之间的关系即可。

我们可以分成两个方面来思考,如果当前点选会怎样,不选又会怎样。因此我们就可以设 \(dp[x][0]\) 表示当前点不选, \(dp[x][1]\) 表示当前点要选。

如果当前点要选的话,他的孩子肯定都不选,,但是不要忘了还有自身的值,所以有:

\[dp[x][1]=\sum_{j\in son[x]}^{j\neq fa}dp[j][0]
\]

如果当前点不选的话,他的孩子选或不选都可以,所以取个最大值就可以了。

\[dp[x][0]=\sum_{j\in son[x]}^{j\neq fa}\max (dp[j][0],dp[j][1])
\]

此时答案就一定是 \(\max (dp[1][1],dp[1][0])\)。

#include<bits/stdc++.h>
using namespace std;
const int INF=1e4+10;
int a[INF],p[INF],root;
int dp[INF][2];//0为不选,1为选
vector<int> mp[INF]; void dfs(int x){
int len=mp[x].size();
for (int i=0;i<len;i++){
int t=mp[x][i];
dfs(t);
dp[x][0]+=max(dp[t][0],dp[t][1]);
dp[x][1]+=dp[t][0];
}
dp[x][1]+=a[x];
}
int main(){
int n;
cin>>n;
for (int i=1;i<=n;i++){
cin>>a[i];
}
for (int i=1;i<n;i++){
int u,v;
cin>>u>>v;
mp[v].push_back(u);
p[u]++;
}
for (int i=1;i<=n;i++){
if (p[i]==0){
root=i;
break;
}
}
dfs(root);
cout<<max(dp[root][0],dp[root][1]);
return 0;
}

变式练习

一本通——周年纪念晚会

这道题和没有上司的舞会基本上是一模一样,所以说就不讲了,自己摸索摸索吧。关键点上面都提到了,所以就放个代码吧。

#include<bits/stdc++.h>
using namespace std;
const int INF=1e4+10;
int a[INF],p[INF],root;
int dp[INF][2];//0为不选,1为选
vector<int> mp[INF]; void dfs(int x){
int len=mp[x].size();
for (int i=0;i<len;i++){
int t=mp[x][i];
dfs(t);
dp[x][0]+=max(dp[t][0],dp[t][1]);
dp[x][1]+=dp[t][0];
}
dp[x][1]+=a[x];
}
int main(){
int n;
cin>>n;
for (int i=1;i<=n;i++){
cin>>a[i];
}
for (int i=1;i<=n;i++){
int u,v;
cin>>u>>v;
mp[v].push_back(u);
p[u]++;
}
for (int i=1;i<=n;i++){
if (p[i]==0){
root=i;
break;
}
}
dfs(root);
cout<<max(dp[root][0],dp[root][1]);
return 0;
}

内心OS:两份代码好像一模一样

最小点覆盖

什么是最小点覆盖?

最小点覆盖指在满足每条边的两个端点至少要有一个点被选中这个条件下,使选择的点最少,说的专业一点就是在一棵树中选择最小数量的节点使这些节点所覆盖的遍集包含了树中所有的边。

而战略游戏就是一道典型的此类题目,所以我们还是以这个题来讲。

洛谷——战略游戏

首先我们考虑一下,对于一条边,可能会由父亲来看管,也有可能被儿子所看管,所以我们就可以设 \(dp[x][0]\) 表示当前边他自己来看守,\(dp[x][1]\) 表示当前边他不看守,也就是说让他的儿子来看收。

对于 \(dp[x][0]\) 这种情况,他的儿子的情况是可以随便的,所以就有

\[dp[x][0]=\sum_{j\in son[x]}^{j \ne fa}\min(dp[j][0],dp[j][1])+1
\]

对于 \(dp[x][1]\) 这种情况,这条边一定是由他的儿子来看管,所以就是说一定是他儿看管的和,故而有。

\[dp[x][1]=\sum_{j\in son[x]}^{j \ne fa}dp[j][0]
\]

最后的答案就是在 \(\min (dp[root][0],dp[root][1])\)

#include<bits/stdc++.h>
using namespace std;
const int INF=1e5+10;
int p[INF],dp[INF][2];
vector<int> mp[INF];
void dfs(int x,int fa){
dp[x][1]=1;
int len=mp[x].size();
for (int i=0;i<len;i++){
if (mp[x][i]==fa)continue;
int t=mp[x][i];
dfs(t,x);
dp[x][0]+=dp[t][1];
dp[x][1]+=min(dp[t][0],dp[t][1]);
}
}
int main(){
int n;
cin>>n;
for (int i=1;i<=n;i++){
int k,u;
cin>>u>>k;
for (int j=1;j<=k;j++){
int t;
cin>>t;
p[t]++;
mp[u].push_back(t);
mp[t].push_back(u);
}
}
for (int i=0;i<n;i++){
if (p[i]==0){
dfs(i,-1);
cout<<min(dp[i][0],dp[i][1]);
return 0;
}
}
return 0;
}

变式练习

小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为 root 。

除了 root 之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵树”。 如果 两个直接相连的房子在同一天晚上被打劫 ,房屋将自动报警。

给定树的 root 。返回 在不触动警报的情况下 ,小偷能够盗取的最高金额。

这道题类似与最小覆盖点,但其实严格来说的话应该是最大覆盖点,因为我是要求小偷能盗取到的中最高金额。也就是说当我从儿子转移到父亲的时候,我们应该取得的是max而不是上面的min

#include<bits/stdc++.h>
using namespace std;
const int INF=1e5+10;
int w[INF],dp[INF][2];//自己偷,自己不被偷
vector<int> mp[INF]; void dfs(int x,int fa){
dp[x][0]=w[x];
int len=mp[x].size();
for (int i=0;i<len;i++){
if (mp[x][i]==fa)continue;
int t=mp[x][i];
dfs(t,x);
dp[x][0]+=dp[t][1];
dp[x][1]+=max(dp[t][1],dp[t][0]);//这里和最小覆盖点不同
}
}
int main(){
int n,root;
cin>>n>>root;
for (int i=1;i<=n;i++){
cin>>w[i];
}
for (int i=1;i<n;i++){
int u,v;
cin>>u>>v;
mp[u].push_back(v);
mp[v].push_back(u);
}
dfs(root,0);
cout<<max(dp[root][0],dp[root][1]);
return 0;
}

最小支配集

什么是最小支配集?

这个有点不好说,可以通俗的理解为在公司中,员工之间的关系成一颗树的样子,而其中每个人要么自己就是领导,要么就是和领导有直接的联系,求最少要多少个领导。

Cell Phone Network G这道题就是最小支配集的问题,所以说我们一这道题来讲。

洛谷——Cell Phone Network G

还是像我上面讲的一样,要关注父亲和儿子之间的关系,那么这个地方我们怎么想?是考虑两种方式吗?肯定不是,对于一个点而言会有三种情况:自己有塔(\(dp[x][0]\)),自己没塔但是只被儿子覆盖,(\(dp[x][1]\)),自己没塔但是只被父亲覆盖(\(dp[x][2]\)),基于此,我们是不是也可以设三个方程来分别表示?

如果自己有塔的话,他的儿子有塔和无塔是不是都可以,所以就是三种情况都可以,故而取个最小值就可以。

\[dp[x][0]=\sum_{j\in son[x]}^{j \ne fa} \min(dp[j][0],dp[j][1],dp[j][2])
\]

如果自己没塔,但是被儿子覆盖了,那么我们是不是就要从所有的儿子中选一个最小的来作为放置信号塔的位置?然后其他的儿子在第一种和第二种中选一个最小的来求和即可。(令v为当前选的儿子)

\[dp[x][1]=\min(dp[v][0]+\sum_{j\in son[x]}^{j \ne fa}\min(dp[j][0],dp[j][1])-\min(dp[v][0],dp[v][1]))
\]

如果自己没塔,但是被父亲覆盖了,也就是说儿子一定是属于没有塔并且被儿子覆盖的一类,所以说我们只需要对其求和即可。

\[dp[x][2]=\sum_{j\in son[x]}^{j \ne fa}dp[j][1]
\]

因为根节点没有父亲节点,所以答案就是在 \(\min (dp[root][0],dp[root][1])\)

#include<bits/stdc++.h>
using namespace std;
const int INF=5e5+10;
int dp[INF][3];
vector<int> mp[INF];
void dfs(int x,int fa){
dp[x][0]=1,dp[x][1]=1e8;
int tot=0;
int len=mp[x].size();
for (int i=0;i<len;i++){
if (mp[x][i]==fa)continue;
int t=mp[x][i];
dfs(t,x);
dp[x][0]+=min(dp[t][0],min(dp[t][1],dp[t][2]));
tot+=min(dp[t][0],dp[t][1]);
dp[x][2]+=dp[t][1];
}
if (len==1&&x!=1)return;
for (int i=0;i<len;i++){
if (mp[x][i]==fa)continue;
int t=mp[x][i];
dp[x][1]=min(dp[x][1],dp[t][0]+tot-min(dp[t][0],dp[t][1]));
}
}
int main(){
int n;
cin>>n;
for (int i=1;i<n;i++){
int u,v;
cin>>u>>v;
mp[u].push_back(v);
mp[v].push_back(u);
}
dfs(1,-1);
cout<<min(dp[1][0],dp[1][1]);
return 0;
}

变式练习

一本通——皇宫看守

这道题其实就是非常裸的最小点覆盖,我们只需要分清什么时候由父亲到儿子,什么时候由儿子到父亲就可以了。

#include<bits/stdc++.h>
using namespace std;
const int INF=1e5+10;
int p[INF],dp[INF][3],w[INF];
vector<int> mp[INF];
void dfs(int x,int fa){
int minch=1e8;
dp[x][1]=w[x];
int len=mp[x].size();
for (int i=0;i<len;i++){
if (mp[x][i]==fa)continue;
int t=mp[x][i];
dfs(t,x);
dp[x][0]+=min(dp[t][0],dp[t][1]);
minch=min(minch,dp[t][1]-min(dp[t][0],dp[t][1]));
dp[x][1]+=min(dp[t][0],min(dp[t][1],dp[t][2]));
dp[x][2]+=min(dp[t][0],dp[t][1]);
}
dp[x][0]+=minch;
}
int main(){
int n;
cin>>n;
for (int i=1;i<=n;i++){
int k,u;
cin>>u>>w[u]>>k;
for (int j=1;j<=k;j++){
int t;
cin>>t;
p[t]++;
mp[u].push_back(t);
mp[t].push_back(u);
}
}
for (int i=1;i<=n;i++){
if (p[i]==0){
dfs(i,-1);
cout<<min(dp[i][0],dp[i][1]);
return 0;
}
}
return 0;
}

树上直径

我们之前讲过用dfs求解直径,具体的见这里,但是用dfs来求的话,不能处理有负权的情况,所以说现在我们来说说怎么用树形dp来解决。

首先对于每个点而言,都有可能是在直径上的,那么也就是说每个点而言找到两条经过这个点并且没有交集的线段求和即可。

然而对于一个点我们会有三种情况,分别是向上的路(记作 \(u_1\)),向下的最长路(记作 \(d_1\))和向下的次长路(记作 \(d_2\))。而直径就是这三条路径中最长两条。

但是我们仔细思考一下真的需要三个点吗?现在的答案无非就是 \(\max (u_1+d_1,d_1+d_2)\) ,但是其中的 \(u_1+d_1\) 这种情况是多余的,如果当前点要选 \(u_1\) 的话,在上面一定会有一个点的 \(d_1\) 包含这种情况,就像如图所示:



所以说,我们完全可以把 \(u_1\) 这种情况给去掉,只需要维护向下的最大值和次大值就可以了。所以说现在的问题就是怎么维护。

最大值还是很好维护的,如果 \(d1[j]+w\) 大于 \(d1[x]\) 就说明当前的最大值不是真正的最大值,更新就可以了。

\[d1[x]=\max(d1[j]+w_{x\to j})(j \in son[x])
\]

关键就是次大值怎么维护,其实现在有两种情况,第一种是在最大值被更新的时候,原本的最大值就是当前的次大值,第二种就是最大值没有被更新但是比当前次大值要大的时候,此时的 \(d1[j]+w\) 就是新的次大值。

\[d2[x]=d1[x]_{old}(d1[j]+w_{x\to j}>d1[x]_{old} ~~\&~~j\in son[x])
\]
\[d2[x]=d1[j]+w_{x\to j}(d1[j]+w_{x\to j}\le d1[x]~~\&~~j\in son[x])
\]

那么答案就是在所有点中取 \(d1[x]+d2[x]\) 最大的点。

#include<bits/stdc++.h>
using namespace std;
const int INF=2e5+10;
struct Node{
int p,num;
};
int d1[INF],d2[INF];
vector<Node> mp[INF];
int maxn=INT_MIN; void getdw(int x,int fa){
d1[x]=0,d2[x]=0;//最大值,次大值
int len=mp[x].size();
for (int i=0;i<len;i++){
if (mp[x][i].p==fa)continue;
int t=mp[x][i].p,w=mp[x][i].num;
getdw(t,x);
if (d1[t]+w>d1[x]){
d2[x]=d1[x];
d1[x]=d1[t]+w;
}else if (d1[t]+w>d2[x]){
d2[x]=d1[t]+w;
}
}
maxn=max(maxn,d1[x]+d2[x]);
} int main(){
int n;
cin>>n;
for (int i=1;i<n;i++){
int u,v,w;
cin>>u>>v>>w;
mp[u].push_back({v,w});
mp[v].push_back({u,w});
}
getdw(1,-1);
cout<<maxn;
return 0;
}

变式练习

一本通——旅游规划

这道题我们可以换位思考一下,因为我们不可能把所有的直径都标记出来,所以说我们只能判定一个点在不在直径上,那么这里就要像上面所说的一样,对于一个点要记录三个参数,而不能只记录两个参数,然后在三个参数中选择最大的两个求和,看是否等于最大值就可以了。那么现在的问题就是如何维护 \(up[x]\)。

\(up[x]\) 难道只是简单的 \(up[x]=up[fa]+1\) 吗?显然不是,我们是不是可以在 \(fa\) 这个点稍微拐个弯,拐到另一个岔路去?这样的答案就是另外的一条路。

#include<bits/stdc++.h>
using namespace std;
const int INF=2e5+10;
int d1[INF],d2[INF],up[INF],point[INF];//point记录一个点的向下最长链所经过的点
vector<int> mp[INF];
int maxn=INT_MIN; void getdw(int x,int fa){
d1[x]=0,d2[x]=0;//最大值,次大值
int len=mp[x].size();
for (int i=0;i<len;i++){
if (mp[x][i]==fa)continue;
int t=mp[x][i];
getdw(t,x);
if (d1[t]+1>d1[x]){
d2[x]=d1[x];
d1[x]=d1[t]+1;
point[x]=t;
}else if (d1[t]+1>d2[x]){
d2[x]=d1[t]+1;
}
}
maxn=max(maxn,d1[x]+d2[x]);
} void getup(int x,int fa){
int len=mp[x].size();
for (int i=0;i<len;i++){
if (mp[x][i]==fa)continue;
int t=mp[x][i];
if (point[x]==t){
up[t]=max(up[x]+1,d2[x]+1);
}else {
up[t]=max(up[x]+1,d1[x]+1);
}
getup(t,x);
}
}
int main(){
int n;
cin>>n;
for (int i=1;i<n;i++){
int u,v;
cin>>u>>v;
mp[u].push_back(v);
mp[v].push_back(u);
}
getdw(0,-1);
getup(0,-1);
int cnt=0;
for (int i=0;i<n;i++){
int tot=d1[i]+d2[i]+up[i]-min({d1[i],d2[i],up[i]});
if (tot==maxn)cout<<i<<endl;
}
return 0;
}

浅说树形dp的更多相关文章

  1. 浅说——树形DP

    啊!DP! 顾名思义,树形DP就是在树上所做的动态规划.我们一般所做的动态规划多是线性的,线性DP我们可以从前向后或从后向前两种方法,不妨类比一下,在树上我们同样可以有两种方法,从根向树叶或者从树叶向 ...

  2. 提升——树形DP

    这里讲提高一点的内容,所以没有树形DP基础的,先看一下基础部分: 浅说——树形DP 闲言不表,看第一题. 这道题是典型的树上最长链问题.(就是一个模板题) 给定一棵树,树上共有N个节点(N<=5 ...

  3. poj3417 LCA + 树形dp

    Network Time Limit: 2000MS   Memory Limit: 65536K Total Submissions: 4478   Accepted: 1292 Descripti ...

  4. COGS 2532. [HZOI 2016]树之美 树形dp

    可以发现这道题的数据范围有些奇怪,为毛n辣么大,而k只有10 我们从树形dp的角度来考虑这个问题. 如果我们设f[x][k]表示与x距离为k的点的数量,那么我们可以O(1)回答一个询问 可是这样的话d ...

  5. 【BZOJ-4726】Sabota? 树形DP

    4726: [POI2017]Sabota? Time Limit: 20 Sec  Memory Limit: 128 MBSec  Special JudgeSubmit: 128  Solved ...

  6. 树形DP+DFS序+树状数组 HDOJ 5293 Tree chain problem(树链问题)

    题目链接 题意: 有n个点的一棵树.其中树上有m条已知的链,每条链有一个权值.从中选出任意个不相交的链使得链的权值和最大. 思路: 树形DP.设dp[i]表示i的子树下的最优权值和,sum[i]表示不 ...

  7. 树形DP

    切题ing!!!!! HDU  2196 Anniversary party 经典树形DP,以前写的太搓了,终于学会简单写法了.... #include <iostream> #inclu ...

  8. BZOJ 2286 消耗战 (虚树+树形DP)

    给出一个n节点的无向树,每条边都有一个边权,给出m个询问,每个询问询问ki个点,问切掉一些边后使得这些顶点无法与顶点1连接.最少的边权和是多少.(n<=250000,sigma(ki)<= ...

  9. POJ2342 树形dp

    原题:http://poj.org/problem?id=2342 树形dp入门题. 我们让dp[i][0]表示第i个人不去,dp[i][1]表示第i个人去 ,根据题意我们可以很容易的得到如下递推公式 ...

  10. hdu1561 The more, The Better (树形dp+背包)

    题目链接:http://acm.split.hdu.edu.cn/showproblem.php?pid=1561 思路:树形dp+01背包 //看注释可以懂 用vector建树更简单. 代码: #i ...

随机推荐

  1. Qt编写雷达模拟仿真工具(模拟点/歼击机/航母/发射导弹/爆炸效果/激光雷达等)

    一.简单介绍 雷达模拟仿真工具,主要通过模拟点模拟相关物体,方位.航向角.距离.速度,并且显示相关详情信息可建立跟踪线建立与模拟点联系.可自定义更换模拟点背景达到更加逼真效果,如歼击机,航母发射导弹效 ...

  2. Qt自定义控件集成到全平台QtCreator效果图

  3. .NET 响应式编程 System.Reactive 系列文章(一):基础概念

    .NET 响应式编程 System.Reactive 系列文章(一):基础概念 引言 在现代软件开发中,处理异步事件和数据流已经成为常见的需求,比如用户输入.网络请求.传感器数据等.这些数据流通常是无 ...

  4. PS2021、色环插件Coolorus下载+色环无法拖动问题和没有压感问题的解决

    PS2021+色环插件Coolorus+色环无法拖动问题解决+没有压感问题解决 1.资源下载链接 PS各版本下载: 感谢微博@vposy大佬无私奉献提供的各版本ADOBE软件直装破解: 百度网盘: p ...

  5. Flutter一些概念(一)

    1 简述Flutter是什么以及它的主要优势 Flutter是一种由Google开发的开源移动应用开发框架,可以用于构建高度定制化.美观并且性能卓越的移动应用程序,其主要优势有: 跨平台,一次编码,可 ...

  6. biancheng-Spring MVC

    MVC设计模式简介 http://c.biancheng.net/spring_mvc/ MVC 设计模式一般指 MVC 框架,M(Model)指数据模型层,V(View)指视图层,C(Control ...

  7. IOC操作Bean 管理(基于 xml 方式)

    1.什么是 Bean 管理(0)Bean 管理指的是两个操作(1)Spring 创建对象(2)Spirng 注入属性 2.Bean 管理操作有两种方式(1)基于 xml 配置文件方式实现(2)基于注解 ...

  8. Fractal pg walkthrough Easy

    nmap ┌──(root㉿kali)-[~] └─# nmap -p- -A 192.168.157.233 Starting Nmap 7.94SVN ( https://nmap.org ) a ...

  9. 一篇关于c语言的大补帖

    一晃今年又开始了,作为一个失意的中年技术男,现在的心境真的是五味杂陈.赶紧写一篇吧,我怕过了这个点,今年就在没有那个心情去写了. 因为是基础嘛,从事软件开发以来c或者c++相关的东西断断续续 也刷了差 ...

  10. redis渐进式rehash

    本文分享自天翼云开发者社区<redis渐进式rehash>,作者:l****n Redis是k-v型数据库,其内部设计了一种dict类型的数据结构用来存储键值结构. dict 通常的存储结 ...