树状DP笔记

1自底向上

树形dp以子树为最优解

自底向上的求

如求子树大小

代码

#include<bits/stdc++.h>
using namespace std;
int n;
vector<int> g[100005];
void dfs(int u,int p){
	sz[u] = 1;
	for(int i = 0;i < g[u].size();i++){
		int v = g[u][i];
		if(v == p)continue;
		dfs(v,u);
		sz[u] += sz[v];
	}
}
int main(){
	cin>>n;
	for(int i = 1;i <= n-1;i++){
		int u,v;
		cin>>u>>v;
		g[u].push_back(v);
		g[v].push_back(u);
	}
	return 0;
}

例题

A. 战略游戏

题目描述

Bob 喜欢玩电脑游戏,特别是战略游戏。但是他经常无法找到快速玩过游戏的方法。现在他有个问题。

现在他有座古城堡,古城堡的路形成一棵树。他要在这棵树的节点上放置最少数目的士兵,使得这些士兵能够瞭望到所有的路。

注意:某个士兵在一个节点上时,与该节点相连的所有边都将能被瞭望到。

请你编一个程序,给定一棵树,帮 Bob 计算出他最少要放置的士兵数。

输入格式

输入数据表示一棵树,描述如下。

第一行一个数  ,表示树中节点的数目。

第二到第  行,每行描述每个节点信息,依次为该节点编号 ,数值 , 表示后面有  条边与节点  相连,接下来  个数,分别是每条边的所连节点编号 。

对于一个有  个节点的树,节点标号在  到  之间,且在输入文件中每条边仅出现一次。

输出格式

输出仅包含一个数,为所求的最少士兵数。

样例
样例输入复制
4
0 1 1
1 2 2 3
2 0
3 0
样例输出复制
1

AC代码

#include<bits/stdc++.h>
using namespace std;
int n;
vector<int> g[100005];
int dp[100005][2];
void dfs(int u,int p){
	dp[u][0] = 0,dp[u][1] = 1;
	for(int i = 0;i < g[u].size();i++){
		int v = g[u][i];
		if(v == p)continue;
		dfs(v,u);
		dp[u][0] += dp[v][1];
		dp[u][1] += min(dp[v][0],dp[v][1]);
	}
}
int res[100005];
int main(){
	cin>>n;
	for(int i = 1;i <= n;i++){
		int u,k,v;
	    cin>>u>>k;
	    while(k--){
	    	cin>>v;
	    	g[u].push_back(v);
			}
	}
	dfs(0,0);
	cout<<min(dp[0][0],dp[0][1]);
	return 0;
}

背包树上

普通背包的改版

#include<bits/stdc++.h>
using namespace std;
vector<int> g[100005];
int f[1005][1005];//f(i,j)表示以i为节点选j门课 
int sz[100005];
int n,m;
int a[1005];
int res[100005];
void F(int u){
	f[u][1] = a[u];
	sz[u] = 1;
	for(int k = 0;k < g[u].size();k++){
		int v = g[u][k];
		F(v);
		sz[u]+=sz[v];
		for(int i = min(m,sz[u]);i >= 1;i--){
			for(int j = 0;j < i&&j <= sz[v];j++){
				f[u][i] = max(f[u][i],f[u][i-j]+f[v][j]);
			}
		}
	}
}
int main(){
	cin>>n>>m;
	m++;
    for(int i = 1;i <= n;i++){
    	int u;
    	cin>>u>>a[i];
    	g[u].push_back(i);
	}
	F(0);
	cout<<f[0][m];
	return 0;
}

换根DP

核心思路:以原始版本 来 求出变化

#include<bits/stdc++.h>
using namespace std;
vector<int> g[1000005];
long long dp[1000005];
long long sz[1000005];
int n,m;
void dfs(int u,int p){
	dp[u] = 0;
	sz[u] = 1;
	for(int v:g[u]){
	    if(v == p)continue;
	    dfs(v,u);
	    sz[u] +=sz[v];
	    dp[u] += dp[v] + sz[v];
	}
}
void dfs2(int u,int p){
	for(int v:g[u]){
	    if(v == p)continue;
	    dp[v] += (dp[u]-(dp[v]+sz[v])+n -sz[v]);
	    dfs2(v,u);
	    
	}
}
int main(){
	cin>>n;
    for(int i = 1;i <= n-1;i++){
    	int u,v;
    	cin>>u>>v;
    	g[u].push_back(v);
    	g[v].push_back(u);
	}
    dfs(1,1);
    dfs2(1,1);
    long long id,mm = 0;
    for(int i = 1;i <= n;i++){
   	if(mm < dp[i])mm = dp[i],id = i;
        
	}
	cout<<id;
	return 0;
}

 例题2 没有上司的舞会

题目描述

某大学有 �n 个职员,编号为 1…�1…n。

他们之间有从属关系,也就是说他们的关系就像一棵以校长为根的树,父结点就是子结点的直接上司。

现在有个周年庆宴会,宴会每邀请来一个职员都会增加一定的快乐指数 ��ri​,但是呢,如果某个职员的直接上司来参加舞会了,那么这个职员就无论如何也不肯来参加舞会了。

所以,请你编程计算,邀请哪些职员可以使快乐指数最大,求最大的快乐指数。

输入格式

输入的第一行是一个整数 �n。

第 22 到第 (�+1)(n+1) 行,每行一个整数,第 (�+1)(i+1) 行的整数表示 �i 号职员的快乐指数 ��ri​。

第 (�+2)(n+2) 到第 2�2n 行,每行输入一对整数 �,�l,k,代表 �k 是 �l 的直接上司。

输出格式

输出一行一个整数代表最大的快乐指数。

输入输出样例

输入 #1复制

7
1
1
1
1
1
1
1
1 3
2 3
6 4
7 4
4 5
3 5

输出 #1复制

5

说明/提示

数据规模与约定

对于 100%100% 的数据,保证 1≤�≤6×1031≤n≤6×103,−128≤��≤127−128≤ri​≤127,1≤�,�≤�1≤l,k≤n,且给出的关系一定是一棵树。

AC代码

#include<bits/stdc++.h>
using namespace std;
int n;
vector<int> g[100005];
int dp[100005][2];//0不去,1去 
int r[100005];
void dfs(int u,int p){
	dp[u][0] = 0,dp[u][1] = r[u]; 
	for(int i = 0;i < g[u].size();i++){
		int v = g[u][i];
		if(v == p)continue;
		dfs(v,u);
		dp[u][0] +=max(dp[v][0],dp[v][1]);
		dp[u][1] += dp[v][0];
	}
}
int res[100005];
int t;
bool pd[100005];
int main(){
	cin>>n;
	for(int i = 1;i <= n;i++){
	    cin>>r[i];
	}
	dfs(0,0);
	for(int i = 1;i <= n-1;i++){
		int l,k;
		cin>>l>>k;
		g[k].push_back(l);
		pd[l] = 1;
	}
	cin>>t>>t;
	for(int i = 1;i <= n;i++){
		if(!pd[i])t = i;
	}
	dfs(t,t);
	cout<<max(dp[t][0],dp[t][1]);
	return 0;
}

 换根DP 2

# [USACO10MAR] Great Cow Gathering G

## 题目描述

Bessie 正在计划一年一度的奶牛大集会,来自全国各地的奶牛将来参加这一次集会。当然,她会选择最方便的地点来举办这次集会。

每个奶牛居住在 $N$ 个农场中的一个,这些农场由 $N-1$ 条道路连接,并且从任意一个农场都能够到达另外一个农场。道路 $i$ 连接农场 $A_i$ 和 $B_i$,长度为 $L_i$。集会可以在 $N$ 个农场中的任意一个举行。另外,每个牛棚中居住着 $C_i$ 只奶牛。

在选择集会的地点的时候,Bessie 希望最大化方便的程度(也就是最小化不方便程度)。比如选择第 $X$ 个农场作为集会地点,它的不方便程度是其它牛棚中每只奶牛去参加集会所走的路程之和(比如,农场 $i$ 到达农场 $X$ 的距离是 $20$,那么总路程就是 $C_i\times 20$)。帮助 Bessie 找出最方便的地点来举行大集会。

## 输入格式

第一行一个整数 $N$ 。

第二到 $N+1$ 行:第 $i+1$ 行有一个整数 $C_i$。

第 $N+2$ 行到 $2N$ 行:第 $i+N+1$ 行为 $3$ 个整数:$A_i,B_i$ 和 $L_i$。

## 输出格式

一行一个整数,表示最小的不方便值。

## 样例 #1

### 样例输入 #1

```






1 3 1 
2 3 2 
3 4 3 
4 5 3
```

### 样例输出 #1

```
15
```

## 提示

$1\leq N\leq 10^5$,$1\leq A_i\leq B_i\leq N$,$0 \leq C_i,L_i \leq 10^3$。

 

 AC代码

#include<bits/stdc++.h>
using namespace std;
struct kkk {
	int v,l;
};
vector<kkk> g[1000005];
long long dp[1000005];
long long sz[1000005];
int n,m;
long long c[1000005];
void dfs(int u,int p){
	dp[u] = 0;
	sz[u] = c[u];
	for(kkk way:g[u]){
		int v = way.v,l = way.l;
	    if(v == p)continue;
	    dfs(way.v,u);
	    dp[u]+=dp[v]+sz[v]*l;
	    sz[u]+=sz[v];
	}
}
void dfs2(int u,int p){
	for(kkk way:g[u]){
		int v = way.v,l = way.l;
	    if(v == p)continue;
	    dp[v] +=(dp[u]-(dp[v]+sz[v]*l)+(sz[1]-sz[v])*l);
	    dfs2(way.v,u);
	   
	}
}
int main(){
	cin>>n;
	for(int i = 1;i <= n;i++){
		cin>>c[i];
	}
	
    for(int i = 1;i <= n-1;i++){
    	int u,v,l;
    	cin>>u>>v>>l;
    	g[u].push_back(kkk{v,l});
    	g[v].push_back(kkk{u,l});
	}
    dfs(1,1);
    dfs2(1,1);
    long long id = 1e16,mm = 0;
    for(int i = 1;i <= n;i++){
   	   id = min(dp[i],id);
        
	}
	cout<<id;
	return 0;
}

换根例题3

题目描述

W 市的交通规划出现了重大问题,市政府下定决心在全市各大交通路口安排疏导员来疏导密集的车流。但由于人员不足,W 市市长决定只在最需要安排人员的路口安排人员。

具体来说,W 市的交通网络十分简单,由  个交叉路口和  条街道构成,交叉路口路口编号依次为  。任意一条街道连接两个交叉路口,且任意两个交叉路口间都存在一条路径互相连接。

经过长期调查,结果显示,如果一个交叉路口位于 W 市交通网最长路径上,那么这个路口必定拥挤不堪。所谓最长路径,定义为某条路径 ,路径经过的路口各不相同,且城市中不存在长度大于  的路径,因此最长路径可能不唯一。因此 W 市市长想知道哪些路口位于城市交通网的最长路径上。

输入格式

第一行一个整数 ;

之后  行每行两个整数 ,表示  和  的路口间存在着一条街道。

输出格式

输出包括若干行,每行包括一个整数——某个位于最长路径上的路口编号。为了确保解唯一,请将所有最长路径上的路口编号按编号顺序由小到大依次输出。

样例
样例输入复制
10
0 1
0 2
0 4
0 6
0 7
1 3
2 5
4 8
6 9
样例输出复制
0
1
2
3
4
5
6
8
9

AC代码

#include<bits/stdc++.h>
using namespace std;
vector<int> g[1000005];
long long dp1[1000005],dp2[1000005];
long long sz[1000005];
int n,m;
void dfs(int u,int p){
	for(int v:g[u]){
	    if(v == p)continue;
	    dfs(v,u);
	   if(dp1[v]+1 > dp1[u])dp2[u] = dp1[u],dp1[u] =dp1[v]+1;
	   else if(dp1[v]+1 > dp2[u])dp2[u] = dp1[v]+1;
	}
}
void dfs2(int u,int p){
	for(int v:g[u]){
		int tmp;
	    if(v == p)continue;
	    if(dp1[v]+1 != dp1[u])tmp = dp1[u];
	    else tmp = dp2[u];
	    if(tmp+1 >dp1[v])dp2[v] = dp1[v],dp1[v] = tmp+1;
	    else if(tmp+1 > dp2[v])dp2[v] = tmp+1;
	    dfs2(v,u);
	}
}
int main(){
	cin>>n;
    for(int i = 1;i <= n-1;i++){
    	int u,v;
    	cin>>u>>v;
    	g[u].push_back(v);
    	g[v].push_back(u);
	}
    dfs(0,0);
    dfs2(0,0);
    long long id,mm = 0;
    for(int i = 0;i <= n;i++){
        mm = max(mm,dp1[i]+dp2[i]);
	}
	for(int i = 0;i <= n;i++){
		if(mm == dp1[i]+dp2[i])cout<<i<<endl;
	}
	return 0;
}

 

 (完)

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值