目录
CodeForces - 1118F1 Tree Cutting
树形dp:
树形dp,在树上进行的dp。首先考虑存树。
第一种是用结构体存,但是需要固定的孩子结点,不方便。
第二种既方便又好理解使用vector,每一个vec[i]里面存放的是i结点下一个孩子(如果输入时无法确定父子关系,双向存储,遍历的时候标记父节点,因为树的特性,只要标记了父节点,就不会回去),本质上就是用二维数组进行存储,如果需要点的权值就用add[]数组加权,边的权值就用add[u][v]=add[v][u]=w加权(如果知道父子关系就只需要add[u][v]=w即可)。
第三种对边权值比较有利,并且可以存重边(对边有优势)的链式前向星。链式前向星需要一个head数组和一个含有三个变量(to,next,w)的结构体。对有向图:head[u]表示以结点u为起点的按输入顺序的最后一条边的编号,初始化为-1.结构体edge存边的数据,edge[cnt]表示输入的第cnt条边(这里的cnt就是编号,按输入顺序从1-cnt)。其中to表示该边的终点,next表示上一条边的编号,也就是说,当一条新边加入时,这时候对应的head应该指向这条边,而这条边的next就应该是原本head表示的边。这样对于每一个点,由head[u]开始第一条边的编号,然后edge的next到下一条边,一直到-1时终止,就可以遍历完所有的边了。对于无向图,u->v添加完,再添加v->u即可。
CodeForces - 1118F1 Tree Cutting
题目大意:
有这么一颗树,每个节点有三种可能的颜色,红色、蓝色或者无色,问你有多少种分割方式可以把树分成两半并且保证红色和蓝色分别位于两边。
2<=n<=3e5
思路:
遍历所有树枝,看树枝两侧颜色分布,可以看一侧是否红色颜色数等于总数蓝色为0或者蓝色颜色数等于总数红色为0。如果每一次都搜的话为T掉,发现颜色分布始终不变,所以可以记录一下,树形dp:树上进行dp。
因为树的特性,可以以任意一点为根。这里以1为整个树的根,详细见代码。
代码:
#include<algorithm>
#include<iostream>
#include<cstring>
#include<string>
#include<cstdlib>
#include<map>
#include<cmath>
#include<vector>
#include<cstdio>
using namespace std;
typedef long long ll;
const int maxn = 3e5+50;
vector<int>vec[maxn];
int add[maxn];
int n;
int dp_red[maxn];//记录以u为根的子树的红色个数
int dp_blue[maxn];//记录以u为根的子树的蓝色个数
int ans;
int red,blue; //记录总共的红色结点数、蓝色结点数
void dfs(int u,int pre){
if(add[u] == 1) dp_red[u]=1;
else if(add[u] == 2) dp_blue[u]=1;
for(int i = 0;i < vec[u].size();i++){
int y = vec[u][i];
if(y == pre) continue;//因为双向,防止循环回去
dfs(y,u);//统计完以y为根的子树的红色蓝色结点数
dp_red[u] += dp_red[y];//更新
dp_blue[u] += dp_blue[y];
}
//判断,这里u结点可行表示u的上层树枝可以选取。(点与树枝一一对应)
if((dp_red[u]==red&&dp_blue[u]==0)||(dp_red[u]==0&&dp_blue[u]==blue)) ans++;
}
int main(){
cin >> n;
red=0;blue=0;
for(int i = 1;i <= n;i++){
cin >> add[i];
if(add[i] == 1) red++;
else if(add[i] == 2) blue++;
}
int v,u;
for(int i = 1;i < n;i++){
cin >> v >> u;//因为不确定先后等级,所有做双向。
vec[v].push_back(u);
vec[u].push_back(v);
}
dfs(1,0);
cout<<ans<<endl;
return 0;
}
树形背包dp:
预知识:分组背包,有依赖的背包
洛谷-选课:
在大学里每个学生,为了达到一定的学分,必须从很多课程里选择一些课程来学习,在课程里有些课程必须在某些课程之前学习,如高等数学总是在其它课程之前学习。现在有 N门功课,每门课有个学分,每门课有一门或没有直接先修课(若课程 a 是课程 b 的先修课即只有学完了课程 a,才能学习课程 b)。一个学生要从这些课程里选择 M 门课程学习,问他能获得的最大学分是多少?
分析:
看似是森林,其实可以看做以虚拟节点0为根的树。先修课为父节点,这样就形成了树,对于树上进行背包dp-树形背包dp。--有依赖的背包。
核心代码:
for(int i = 0;i < vec[u].size();i++){
int v = vec[u][i];
dfs(v);
sum[u] += sum[v];
for(int j = min(m,sum[u]);j >= 1;j--){
for(int k = 0;k <= min(j-1,sum[v]);k++){
dp[u][j] = max(dp[u][j],dp[u][j-k]+dp[v][k]);
}
}
}
K++是因为每个结点只占据一个空间。
外层就是组别种类数。递归式动态规划,dfs后所有dp[v]全部得到
#include<algorithm>
#include<iostream>
#include<cstring>
#include<string>
#include<cstdlib>
#include<map>
#include<cmath>
#include<vector>
#include<cstdio>
using namespace std;
typedef long long ll;
int dp[500][500];
int n,m;
vector<int>vec[500];
int add[300];
int sum[300];//记录结点v对应的分支结点数、即科目数
void dfs(int u){
sum[u] = 1;
dp[u][1] = add[u];
for(int i = 0;i < vec[u].size();i++){
int v = vec[u][i];
dfs(v);
sum[u] += sum[v];
for(int j = min(m,sum[u]);j >= 1;j--){
for(int k = 0;k <= min(j-1,sum[v]);k++){
dp[u][j] = max(dp[u][j],dp[u][j-k]+dp[v][k]);
}
}
}
}
int main(){
cin >> n >> m;
int x;
for(int i = 1;i <= n;i++){
cin >> x >> add[i];
vec[x].push_back(i);
}
m++;
dfs(0);
cout<<dp[0][m]<<endl;
return 0;
}
洛谷-二叉苹果树
代码 :
#include<algorithm>
#include<iostream>
#include<cstring>
#include<string>
#include<cstdlib>
#include<map>
#include<cmath>
#include<vector>
#include<cstdio>
using namespace std;
typedef long long ll;
const int maxn = 1e6+50;
vector<int>vec[300];
int w[300][300];
int dp[300][300];
int sum[300];
int n,q;
void dfs(int u,int pre){
if(u != 1)
sum[u]=1;
for(int i = 0;i < vec[u].size();i++){
int v = vec[u][i];
if(v == pre) continue;
dfs(v,u);
sum[u] += (sum[v]+1);
for(int j = min(sum[u],q);j >= 1;j--){
for(int k = 0;k <= min(sum[v],j-1);k++)
dp[u][j] = max(dp[u][j],dp[u][j-k-1]+dp[v][k]+w[u][v]);
}
}
}
int main(){
cin >> n >> q;
int u,v,s;
for(int i = 1;i < n;i++){
cin >> u >> v >> s;
w[u][v] = w[v][u] = s;
vec[u].push_back(v);
vec[v].push_back(u);
}
dfs(1,0);
cout<<dp[1][q]<<endl;
return 0;
}
运用链式前向星:
#include<algorithm>
#include<iostream>
#include<cstring>
#include<string>
#include<queue>
#include<map>
#include<cstdio>
#include<cmath>
#include<stdlib.h>
using namespace std;
typedef long long ll;
int head[300];
int cnt = 0;
struct edges{
int to;
int next;
int w;
};
int dp[300][300];
edges edge[300];
int sum[300];
void add(int u,int v,int w){
edge[cnt].to = v;
edge[cnt].w=w;
edge[cnt].next = head[u];
head[u] = cnt;
cnt++;
}
int n,q;
void dfs(int u,int pre){
for(int i = head[u];i != -1;i = edge[i].next){
int v = edge[i].to;
if(v == pre) continue;
dfs(v,u);
sum[u]+=(sum[v]+1);
for(int j = min(sum[u],q);j >= 1;j--){
for(int k = 0;k <= min(j-1,q);k++){
dp[u][j] = max(dp[u][j],dp[u][j-k-1]+dp[v][k]+edge[i].w);
}
}
}
}
int main(){
cin >> n >> q;
int x,y,w;
for(int i = 0;i <= n;i++) head[i] = -1;
for(int i = 1;i < n;i++){
cin >> x >> y >> w;
add(x,y,w);
add(y,x,w);
}
dfs(1,0);
cout<<dp[1][q]<<endl;
return 0;
}