树形dp
树形dp
例题一 没有上司的舞会
做法
过于经典,不多赘述
#include<bits/stdc++.h>
using namespace std;
const int maxn=6*1e3+5;
int f[maxn][2],num[maxn],n,x,y,root;
vector<int>d[maxn];
void dfs(int now,int last){
	f[now][0]=f[now][1]=0;
	for(int i=0;i<d[now].size();i++){
		int Next=d[now][i];
		if(Next==last)continue;
		dfs(Next,now);
		f[now][0]+=max(f[Next][1],f[Next][0]);
		f[now][1]+=f[Next][0];
	}
	f[now][1]+=num[now];
	return ;
}
int main(){
	cin>>n;
	for(int i=1;i<=n;i++)cin>>num[i];
	for(int i=1;i<n;i++){
		cin>>x>>y;
		root+=x;
		d[y].push_back(x);
	}
	root=(1+n)*n/2-root;
	dfs(root,root);
	cout<<max(f[root][0],f[root][1]);
	return 0;
}
例题二 最大利润
题目大意
John邀请了你在火车站开饭店,但不允许同时在两个相连接的火车站开。
任意两个火车站有且只有一条路径,每个火车站最多有50个和它相连接的火车站。
告诉你每个火车站的利润,问你可以获得的最大利润为多少。
代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+5;
int f[maxn][3],num[maxn],n,x,y;
vector<int>d[maxn];
void dfs(int now,int last){
	f[now][0]=f[now][1]=0;
	for(int i=0;i<d[now].size();i++){
		int Next=d[now][i];
		if(Next==last)continue;
		dfs(Next,now);
		f[now][0]+=max(f[Next][0],f[Next][1]);
		f[now][1]+=f[Next][0];
	}
	f[now][1]+=num[now];
	return ;
}
int main(){
	cin>>n;
	for(int i=1;i<=n;i++)cin>>num[i];
	for(int i=1;i<n;i++){
		cin>>x>>y;
		d[x].push_back(y);
		d[y].push_back(x);
	}
	dfs(1,1);
	cout<<max(f[1][0],f[1][1]);
	return 0;
}
例题三 P - Independent Set
题目大意
一棵树有n个结点,你要给每个点染上黑色或者白色,但是任意相邻两个结点不能同时染成黑色。
问有多少种不同的染色方案,答案模1000000007。
代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=1e5+5,p=1e9+7;
int n,x,y,f[maxn][3];
vector<int>d[maxn];
void dfs(int now,int last){
	f[now][0]=f[now][1]=1;
	for(int i=0;i<d[now].size();i++){
		int Next=d[now][i];
		if(Next==last)continue;
		dfs(Next,now);
		f[now][1]=(f[now][1]*f[Next][0])%p;
		f[now][0]=f[now][0]*(f[Next][1]+f[Next][0])%p;
	}
	return ;
}
signed main(){
	cin>>n;
	for(int i=1;i<n;i++){
		cin>>x>>y;
		d[x].push_back(y);
		d[y].push_back(x);
	}
	dfs(1,1);
	cout<<(f[1][0]+f[1][1])%p;
	return 0;
}
例题四 移动信号
做法
记f[now][0]为now建
记f[now][1]为now不建,没信号
记f[now][2]为now不建,有信号
考虑f[now][2],首先直接从min(f[next][0],f[next][2])
但有可能转移过来的全部都是f[next][2],此时now就没有信号
我们就要强制一个next将f[next][0]转移过来
代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e4+5,oo=1e9+7;
int f[maxn][3],n,x,y;
vector<int>d[maxn];
void dfs(int now,int last){
	f[now][0]=1;
	f[now][1]=0;
	f[now][2]=0;
	bool flag=false;
	int sum=0;
	for(int i=0;i<d[now].size();i++){
		int Next=d[now][i];
		if(Next==last)continue;
		dfs(Next,now);
		f[now][0]+=min(min(f[Next][2],f[Next][1]),f[Next][0]);
		f[now][1]+=min(f[Next][0],f[Next][2]);
		f[now][2]+=min(f[Next][2],f[Next][0]);
		if(f[Next][2]>=f[Next][0])flag=true;
	}
	if(!flag)sum=f[now][2];
	else return ;
	f[now][2]=oo;
	for(int i=0;i<d[now].size();i++){
		int Next=d[now][i];
		if(Next==last)continue;
		f[now][2]=min(f[now][2],sum-f[Next][2]+f[Next][0]);
	}
	return ;
}
int main(){
	cin>>n;
	for(int i=1;i<n;i++){
		cin>>x>>y;
		d[x].push_back(y);
		d[y].push_back(x);
	}
	dfs(1,1);
//	for(int i=1;i<=n;i++)cout<<i<<" "<<f[i][0]<<" "<<f[i][1]<<" "<<f[i][2]<<endl;
	cout<<min(f[1][2],f[1][0]);
	return 0;
}
例题五 选边
题目大意
一棵树有n个结点,编号1至n。有n-1条边,第i条边是:u[i],v[i],w[i]表示结点u[i]与结点v[i]有一条权值为w[i]的无向边。
你需要从这n-1条边当中选取若干条边(可以不选),使得被选中的边的权值权值总和最大,但是有一些限制:
对于第i个点来说,与点i相连的所有边当中最多只能有d[i]条边被选中。
做法
记f[now][0]表示在now子树符合规则的选边的最大权值和,其中now结点不与其父亲结点的连边。
记f[now][1]表示在now子树符合规则的选边的最大权值和,其中now结点与其父亲结点的连边,即now最多只能与d[now]-1个儿子连边。
先让 f[now][0] = sum(f[son][0],son是now儿子),然后尝试通过选中now-->son这条边,调整它的最大值,
因为可能存在儿子son,使得w[now][son] + f[son][1] > f[son][0],这样会使得f[now][0]增加w[now][son] + f[son][1] - f[son][0],我们称满足这些条件的
son叫做有用儿子,把w[now][son] + f[son][1] - f[son][0]叫做增大量。我们可以把所有这些有用儿子的增大量全部收集起来,保存在数组g[0...cnt-1],
然后对g从大到小排序,把最大的cnt的增加量累加到f[now][[0]即可得到最优的f[now][0]。
f[now][1]的值和f[now][0]几乎一样,只是有用儿子的增大量最多只能取cnt-1个增加量。
代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=3*1e5+5;
struct edge{
	int to,v;
};
bool cmp(int x,int y){
	return x>y;
}
int f[maxn][3],g[maxn],cnt=0,a[maxn],n,x,y,z;
vector<edge>d[maxn];
void dfs(int now,int last){
	f[now][0]=f[now][1]=0;
	int sum=0;
	for(int i=0;i<d[now].size();i++){
		int Next=d[now][i].to;
		if(Next==last)continue;
		dfs(Next,now);
		sum+=f[Next][0];
	}
	int cnt=0;
	for(int i=0;i<d[now].size();i++){
		int Next=d[now][i].to;
		if(Next==last)continue;
		if(a[Next]>0&&d[now][i].v+f[Next][1]>f[Next][0])g[++cnt]=d[now][i].v+f[Next][1]-f[Next][0];
	}	
	sort(g+1,g+cnt+1,cmp);
	f[now][0]=f[now][1]=sum;
	for(int i=1;i<=min(cnt,a[now]);i++){
		f[now][0]+=g[i];
		if(i==a[now])continue;
		else f[now][1]+=g[i];
	}
	return ;
}
signed main(){
	cin>>n;
	for(int i=1;i<=n;i++)cin>>a[i];
	for(int i=1;i<n;i++){
		cin>>x>>y>>z;
		d[x].push_back({y,z});
		d[y].push_back({x,z});
	}
	dfs(1,1);
//	for(int i=1;i<=n;i++)cout<<i<<" "<<f[i][0]<<" "<<f[i][1]<<endl;
	cout<<f[1][0];
	return 0;
}
例题六 交通违法
题目大意
禅城区有N条双向道路和N个路口,路口的编号从1至N。每条道路连接两个路口。
两个路口之间不会有重边。保证任意两个路口都是相互到达的。
现在觉得在一些路口装上摄像头,检测路面的违法情况。
装在路口的摄像头可以监测到所有连接到这个路口的道路。
现在的问题是:最少需要在多少个路口安装摄像头才能监测所有的道路?
做法
容易看出是基环树。
通过dfs1容易找出换上相邻的两个点X和Y。
那么为了使得X与Y之间道路被监控到,那么X要装摄像头或者Y要装摄像头(当然,两个都装也是可以的)。
假设X安装了摄像头,那么可以删掉X与Y之间的那条边,就构成一棵树了,从X开始DFS2,就是普通的树型DP了,此时最优解是f[X][1]。
假设Y安装了摄像头,那么可以删掉X与Y之间的那条边,就构成一棵树了,从Y开始DFS2,就是普通的树型DP了,此时最优解是f[Y][1]。
答案等于min(f[X][1],f[Y][1])
代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+5;
bool flag=true,vis[maxn]={false};
int st,en,cnt=0,n,m,x,f[maxn][3];
vector<int>d[maxn];
void dfs1(int now,int last){
	if(!flag||vis[now]){
		if(flag){
			st=last;
			en=now;
			flag=false;
		}
		return ;
	}
	vis[now]=true;
	for(int i=0;i<d[now].size();i++){
		int Next=d[now][i];
		if(Next==last)continue;
		dfs1(Next,now);
	}
	vis[now]=false;
	return ;
}
bool cmp(int A,int B){
	return A==st&&B==en||A==en&&B==st;
}
void dfs2(int now,int last){
//	printf("come to this:now is %d,last is %d\n",now,last);
	f[now][0]=1;
	f[now][1]=0;
//	int minn=1e9+7;
	for(int i=0;i<d[now].size();i++){
		int Next=d[now][i];
		if(Next==last||cmp(Next,now))continue;
//		printf("i is %d ,now is to Next:%d ,now is %d;last is %d\n",i,Next,now,last);
		dfs2(Next,now);
		f[now][0]+=min(f[Next][0],f[Next][1]);
		f[now][1]+=f[Next][0];
//		minn=min(minn,-min(f[Next][0],f[Next][1])+f[Next][1]);
	}
//	if(minn!=1e9+7)
//	f[now][1]+=minn;
	return ;
}
int main(){
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>m;
		for(int j=1;j<=m;j++){
			cin>>x;
			d[i].push_back(x);
		}
	}
//	for(int i=1;i<=n;i++){
//		cin>>m>>x;
//		d[m].push_back(x);
//		d[x].push_back(m);
//	}
	dfs1(1,1);
//	cout<<cmp(1,3)<<" "<<cmp(2,3)<<" "<<cmp(3,1)<<endl;
//	cout<<st<<" "<<en<<endl;
	dfs2(st,st);
	int ans1=f[st][0];
//	for(int i=1;i<=n;i++)cout<<i<<" "<<f[i][0]<<" "<<f[i][1]<<endl;
	
	dfs2(en,en);
	int ans2=f[en][0];
//	for(int i=1;i<=n;i++)cout<<i<<" "<<f[i][0]<<" "<<f[i][1]<<endl;
	cout<<min(ans1,ans2);
	return 0;
}
树形背包合并
例一 选课
方法一:O(n*m^2)
//n*m^2
#include<bits/stdc++.h>
using namespace std;
const int maxn=305;
vector<int>d[maxn];
int n,m,x,y,f[maxn][maxn],a[maxn];
void dfs(int now,int last){
	f[now][1]=a[now];
	for(int i=0;i<d[now].size();i++){
		int Next=d[now][i];
		if(Next==last)continue;
		dfs(Next,now);
		for(int j=m;j>=1;j--){
			for(int k=1;k<=j;k++){
				f[now][j]=max(f[now][j],f[now][k]+f[Next][j-k]);
			}
		}
	}
	return ;
}
int main(){
	cin>>n>>m;
	m++;
	for(int i=1;i<=n;i++){
		cin>>x>>y;
		a[i]=y;
		d[x].push_back(i);
	}
	dfs(0,0);
	cout<<f[0][m];
	return 0;
}
方法二:O(n*m)
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e3+5;
vector<int>d[maxn];
int f[maxn][maxn];
int a[maxn];
int n,m,x,y;
int dfs(int now,int last){
	f[now][1]=a[now];
	int cnt=1;
	for(int i=0;i<d[now].size();i++){
		int Next=d[now][i];
		if(Next==last)continue;
		int cntv=dfs(Next,now);
		for(int j=min(m,cnt+cntv);j>=1;j--){
			for(int k=max(1,j-cntv);k<=min(j,cnt);k++){
				f[now][j]=max(f[now][j],f[now][k]+f[Next][j-k]);
			}
		}
		cnt+=cntv;
	}
	return cnt;
}
int main(){
	
	cin>>n>>m;
	m++;
	for(int i=1;i<=n;i++){
		cin>>x>>y;
		a[i]=y;
		d[x].push_back(i);
	}
	dfs(0,0);
	cout<<f[0][m];
	return 0;
}
例题二 第K大
题目大意
一棵n个结点的树,根是1号点,第i个结点的权值是x[i]。
有Q个问题,第i个问题的格式给出: V[i], K[i],表示询问以V[i]结点为根的子树内,权值第K[i]大的结点的权值是多少。
输入格式
第一行,n和Q。2<=n<=1e5, 1<=Q<=1e5。
第二行,n个整数,第i个整数是x[i], 0 <=x[i]<=1e9。
接下来有n-1行,第i行有两个整数:u[i]和v[i],表示结点u[i]和v[i]之间有一条边。
接下来有Q行,每行描述一个询问,第i行给出V[i]和K[i], 1<=V[i]<=n, 1<=K[i]<=20, 数据保证以V[i]为根的子树的结点总数不少于K[i]。
做法
归并排序
代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+5;
vector<int>d[maxn];
int f[maxn][25],a[maxn],q,n,x,y,tmp[25];
void dfs(int now,int last){
//	cout<<now<<" "<<last<<endl;
	f[now][1]=a[now];
	for(int i=0;i<d[now].size();i++){
		int Next=d[now][i];
		if(Next==last)continue;
		dfs(Next,now);
		int st1=1,st2=1;
		for(int j=1;j<=20;j++){
			if(f[now][st1]>f[Next][st2])tmp[j]=f[now][st1],st1++;
			else tmp[j]=f[Next][st2],st2++;
		}
		for(int j=1;j<=20;j++)f[now][j]=tmp[j];
	}
	return ;
}
int main(){
//	memset(f,0,sizeof f);
	scanf("%d%d",&n,&q);
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i]);
	}
	for(int i=1;i<n;i++){
		scanf("%d%d",&x,&y);
		d[x].push_back(y);
		d[y].push_back(x);
	}
	dfs(1,1);
//	for(int i=1;i<=n;i++){
//		for(int j=1;j<=20;j++)cout<<f[i][j]<<" ";
//		cout<<endl;
//	}
	while(q--){
		scanf("%d%d",&x,&y);
		printf("%d\n",f[x][y]);
	}
	
	
	return 0;
}
例题三 修复道路
代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=155,maxm=155;
int f[maxn][maxm],siz[maxn],n,p,x,y;
vector<int>d[maxn];
void dfs(int now,int last){
	siz[now]=1;
//	f[now][0]=1;
//	if(now==1)f[now][0]--;
	f[now][1]=0;
	for(int i=0;i<d[now].size();i++){
		int Next=d[now][i];
		if(Next==last)continue;
		dfs(Next,now);
		siz[now]+=siz[Next];
		for(int j=min(p,siz[now]);j>=1;j--){
			f[now][j]++;//每多一颗子树,想不变,要多砍一颗
			for(int k=max(j-siz[Next],1);k<=min(siz[now],j);k++){
				f[now][j]=min(f[now][j],f[Next][j-k]+f[now][k]);
			}
		}
	}
//	for(int j=min(p,siz[now]-1);j>=1;j--)f[now][j]++;
	f[now][siz[now]]=0;
	return ;
}
int main(){
	cin>>n>>p;
	memset(f,0x3f,sizeof f);
	int root=(1+n)*n/2;
	for(int i=1;i<n;i++){
		cin>>x>>y;
		d[x].push_back(y);
		root-=y;
	}
	memset(f,0x3f,sizeof f);
	dfs(root,root);
//	for(int i=1;i<=n;i++){
//		cout<<i<<" ";
//		for(int j=0;j<=p;j++)cout<<f[i][j]<<" ";
//		cout<<endl;
//	}
	int minn=1e9;
	for(int i=1;i<=n;i++)minn=min(minn,f[i][p]+((i==root)?0:1));
	cout<<minn;
	return 0;
}
例题四 无向图染色
做法
因可能有多棵树,增加一个虚拟0号结点作为新树的根。
虚拟点的代价无穷大,保证虚拟点不会变成黑色开心结点
本题状态的描述很重要,优雅的分类会使得状态的设计以及状态转移简单很多。
f[i][j][0]表示以i结点为根的子树范围内,已经有j个黑色开心结点,且结点i是白色,的最少费用。
f[i][j][1]表示以i结点根的子树范围内,已经有j个黑色开心结点,且结点i是黑色开心结点,的最少费用。
一、f[i][j][0]的转移:
设结点i共有k个儿子,son[1]...son[k],前k-1个子树有x个黑色开心结点的最小费用记为g[k-1][x]。
可以用f[son[k]][y] + g[k-1][x] 去尝试更新 f[i][x+y][0]
g[k-1]如何求?类似背包合并
记i的前k-2子树有p个黑色开心结点的最小费用记为g[k-2][p]。
可以用 g[k-2][p] + f[son[k-1]][q]去更新 g[k-2][p+q]
二、f[i][j][1]的转移:
1、设结点i共有k个儿子,son[1]...son[k]。
(1)g[k-1][x][0]表示i的前k-1颗子树有x个黑色开心结点,且当前i结点是白色,的最小费用
(2)g[k-1][x][1]表示i的前k-1颗子树有x个黑色开心结点,且当前i已经是黑色开心结点,的最小费用
2、使用g[k-1][x][0]更新f[i][***][1]的情况:
(1)把结点i和结点son[k]一起染成黑色,那么可以用g[k-1][x][0] + f[son[k]][y][0] + money[i] + money[son[k]]
去尝试更新f[now][x+y+2][1],因为本轮染色增加了两个开心结点:结点i和结点son[k]。
(2)把结点i染成黑色,那么可以用g[k-1][x][0] + f[son[k]][y][1] + money[i]
去尝试更新f[now][x+y+1][1],因为本轮染色只增加了1个开心结点:结点i。结点son[k]已经早就是黑色开心结点了
3、使用g[k-1][x][1]更新f[i][***][1]的情况:
(1)使用g[k-1][x][1] + f[son[k]][y][0]去尝试更新f[i][x+y][1],即i的子结点son[k]是白色的情况
(2)使用g[k-1][x][1] + f[son[k]][y][0] + money[son[k]]去尝试更新f[i][x+y+1][1],即把原本是白色的son[k]现在染成黑色的情况
(3)使用g[k-1][x][1] + f[son[k]][y][1]尝试更新f[i][x+y][1]
4、如何计算 g[k][***][0]
(1)可以使用g[k-1][x][0]+f[son[k]][y][0]尝试更新g[k][x+y][0]
(2)可以使用g[k-1][x][0]+f[son[k]][y][1]尝试更新g[k][x+y][0]
5、如何计算 g[k][***][1]
(1) 使用g[k-1][x][1] + f[son[k]][y][0]去尝试更新f[i][x+y][1]
(2) 使用g[k-1][x][1] + f[son[k]][y][0] + money[son[k]]去尝试更新f[i][x+y+1][1],结点son[k]本次会变成黑色开心结点
(3) 使用g[k-1][x][1] + f[son[k]][y][1]去尝试更新f[i][x+y][1]
(4) 使用g[k-1][x][0] + f[son[k]][y][0] + money[i] + money[son[k]]去尝试更新g[k][x+y+2][1]。结点i和结点son[k]一起染成黑色,
因为本轮染色增加了两个开心结点:结点i和结点son[k]。
(5) 使用 g[k-1][x][0] + f[son[k]][y][1] + money[i] 去尝试更新g[k][x+y+1][1]。
因为本轮把结点i染成黑色,为本轮染色只增加了1个开心结点:结点i。
求出f[root][0...n][0]之后,需要回答询问。
注意:答案可能不连续,例如:只有1个黑色开心是不可行,
但是两个黑色开心结点可能是可行的,3个黑色可能是不行的,但4个可以。
而且f[root][i][0]没有单调性,例如可能f[root][3][0] > f[root][4][0]
例如下图,假如是两棵树的森林,如果想要3个黑色开心结点,只能是取{1,2,3},有可能cost[3]非常非常大。
但是如果想要4个黑色开心结点,可以只取{1,2,4,5},可能这4个结点的cost很小。
解决方法一:对于询问Ai,暴力找一个最大的下标i,使得f[root][i][0] <= Ai即可。这方法较慢,需要扫描一遍。
解决方法二:维护单调栈,假如3个黑色开心点需要100元,而4个黑色开心结点只需要50元,那么3个黑色开心点没必要保留。
然后就可以二分答案回答询问了。
代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=1e3+5;
bool vis[maxn];
vector<int>d[maxn];
int f[maxn][maxn][3],a[maxn],siz[maxn],n,m,x,y,q,tmp[maxn][3],B[maxn],A[maxn];
void dfs1(int now,int last){
	vis[now]=true;
	for(int i=0;i<d[now].size();i++){
		int Next=d[now][i];
		if(Next==last)continue;
		dfs1(Next,now);
		
	}
	return ;
}
void MIN(int a,int &b){
	if(a<b)b=a;
	return ;
}
void dfs(int now,int last){
	f[now][0][0]=0;
	siz[now]=1;
//	printf("into %d\n",now);
	for(int i=0;i<d[now].size();i++){
		int Next=d[now][i];
		if(Next==last)continue;
		dfs(Next,now);
//		printf("now is %d ,next is %d\n",now,Next);
		for(int j=0;j<=siz[now];j++){
			tmp[j][0]=f[now][j][0];
			tmp[j][1]=f[now][j][1];
		}
		for(int j=0;j<=siz[now];j++){
			for(int k=0;k<=siz[Next];k++){
				MIN(tmp[j][0]+f[Next][k][0],f[now][j+k][0]);
				MIN(tmp[j][0]+f[Next][k][1],f[now][j+k][0]);
				
				MIN(tmp[j][1]+f[Next][k][1],f[now][j+k][1]);
				MIN(tmp[j][1]+f[Next][k][0],f[now][j+k][1]);
				MIN(tmp[j][0]+f[Next][k][0]+a[Next]+a[now],f[now][j+k+2][1]);
				MIN(tmp[j][1]+f[Next][k][0]+a[Next],f[now][j+k+1][1]);
				MIN(tmp[j][0]+f[Next][k][1]+a[now],f[now][j+k+1][1]);
			}
				
		}
		siz[now]+=siz[Next];
	}
	return ;
}
signed main(){
	scanf("%lld%lld",&n,&m);
	memset(f,0x3f,sizeof f);
	for(int i=1;i<=n;i++)scanf("%lld",&a[i]);
	for(int i=1;i<=m;i++){
		scanf("%lld%lld",&x,&y);
		d[x].push_back(y);
		d[y].push_back(x);
	}
	for(int i=1;i<=n;i++){
		if(vis[i])continue;
		dfs1(i,0);
		d[0].push_back(i);
		d[i].push_back(0);
	}
	a[0]=1e17;
	dfs(0,0);
//	for(int i=0;i<=n;i++){
////		cout<<i<<" "<<siz[i]<<endl;
//		for(int j=0;j<=siz[i];j++){
//			cout<<i<<" "<<j<<" "<<f[i][j][0]<<" "<<f[i][j][1]<<endl;
//			
//		}
//	} 
//	cout<<"af";
	int Top=0;
	
	for(int i=0;i<=siz[0];i++){
		
//		cout<<i<<"ans"<<f[0][i][0]<<"\n";
		if(f[0][i][0]>455743088879883039)continue;
		
		while(Top>0){
			if(B[Top]>f[0][i][0])Top--;
			else break;
		}
		B[++Top]=f[0][i][0];
		A[Top]=i;
//		for(int j=0;j<=Top;j++)cout<<A[j]<<" "<<B[j]<<endl;
//		cout<<endl;
	}
	scanf("%lld",&q);
	while(q--){
		scanf("%lld",&x);
		int l=0,r=Top+1;
		while(l+1<r){
			int mid=(l+r)/2;
			if(B[mid]<=x)l=mid;
			else r=mid;
		}
		printf("%lld\n",A[l]);
	}
	return 0;
}
换根dp
例题一 大集会
代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=1e5+5;
int siz[maxn],a[maxn],f[maxn],tot_size,minn=1e17,n,x,y,z;
struct node{
	int to,v;
};
vector<node>d[maxn];
void dfs(int now,int last,int length){
	siz[now]=a[now];
	f[1]+=length*a[now];
	for(int i=0;i<d[now].size();i++){
		int Next=d[now][i].to;
		if(Next==last)continue;
		dfs(Next,now,length+d[now][i].v);
		siz[now]+=siz[Next];
	}
	return ;
}
void dfs2(int now,int last,int path){
	f[now]=f[last]+path*tot_size-2*path*siz[now];
	minn=min(minn,f[now]);
	for(int i=0;i<d[now].size();i++){
		int Next=d[now][i].to;
		if(Next==last)continue;
		dfs2(Next,now,d[now][i].v);
	}
	return ;
}
signed main(){
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	for(int i=1;i<n;i++){
		cin>>x>>y>>z;
		d[x].push_back({y,z});
		d[y].push_back({x,z});
	}
	dfs(1,1,0);
//	cout<<"af";
	tot_size=siz[1];
	dfs2(1,1,0);
	cout<<minn;
	return 0;
} 
例题二 蚂蚁聚会
题目大意
有n个蚁巢,这n个蚁巢形成一颗树形结构,第i个蚁巢有a[i]只蚂蚁。现在蚂蚁们想举行一个大型的聚会。
但是这些蚂蚁比较懒惰,都不想走太远,每只蚂蚁最多只愿意走X步(每一步就是走一条边)。
它们要计算:如果选择第i个蚁巢作为举行聚会的地点,可以有多少只蚂蚁参加聚会?记该数量为p[i]。
你的任务就是帮助计算: p[1]、p[2]、p[3]、....p[n]。
做法
换根DP
一、定义f[i][j]表示i子树内,跟i结点之间有j条的所有结点上的所有蚂蚁的总数量。
先选1号点为根,dfs1(1,-1)一遍,可以求出f数组,时间复杂度O(nx) 
二、定义g[i][j]表示若以i根整棵树的根,跟i结点之间有j条的所有结点上的所有蚂蚁的总数量。
显然g[1][0...x] = f[1][0...x]
若2是1号结点的儿子结点,如何由g[1][0...x]推出g[2][0...x]?
如何求g[2][i]?
首先在2子树内,走i步到达2号点的蚂蚁,肯定应该算到g[2][i],故先赋值g[2][i] = f[2][i]
还有一部分蚂蚁是从1号结点向下走到2号点的,是哪些蚂蚁呢?
	1、这些蚂蚁不在2号子树内
	2、这些蚂蚁是走i-1步到达1号点,再从1号点走一步到达2号点的
	3、这部分蚂蚁数量等于g[1][i-1] - f[2][i-2]
	4、故g[2][i] = f[2][i] + (g[1][i-1] - f[2][i-2])
	5、边界处理。g[2][0] = a[2], g[2][1] = f[2][1] + g[2][0] 
	6、由g[1][0...x]计算完g[2][0...x],然后再递归2号点,所以遇到叶子结点时,可以直接返回了 
三、求答案
对于每个点i,输出sum(g[i][0...x])即可。 
代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+5;
int g[maxn][25],f[maxn][25],a[maxn];
vector<int>d[maxn];
int n,x,y,X;
void dfs1(int now,int last){
	g[now][0]=a[now];
	for(int i=0;i<d[now].size();i++){
		int Next=d[now][i];
		if(Next==last)continue;
		dfs1(Next,now);
		for(int i=1;i<=X;i++)
			g[now][i]+=g[Next][i-1];
	}
	return ;
}
void dfs2(int now,int last){
	f[now][0]=a[now];
	if(now!=1)
	for(int i=1;i<=X;i++)f[now][i]=f[last][i-1]+g[now][i]-((i==1)?0:g[now][i-2]);
	for(int i=0;i<d[now].size();i++){
		int Next=d[now][i];
		if(Next==last)continue;
		dfs2(Next,now);	
	}
	return ;
}
int main(){
	scanf("%d%d",&n,&X);
	for(int i=1;i<n;i++){
		scanf("%d%d",&x,&y);
		d[x].push_back(y);
		d[y].push_back(x);
	}
	for(int i=1;i<=n;i++)scanf("%d",&a[i]);
	dfs1(1,1);
	for(int i=0;i<=X;i++)f[1][i]+=g[1][i];
	dfs2(1,1);
//	for(int i=1;i<=n;i++){
//		for(int j=0;j<=X;j++)cout<<g[i][j]<<" ";
//		cout<<endl;
//	}
//	cout<<endl;
	for(int i=1;i<=n;i++){
		int ans=0;
		for(int j=0;j<=X;j++)ans+=f[i][j];
		printf("%d\n",ans);
	}
	return 0;
}
例题三 回家
代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=5*1e5+5; 
struct node{
	int to,v;
};
int g[maxn][3],a[maxn],b[maxn],tot,n,K,x,y,z;
vector<node>d[maxn];
void MAX(int &a,int b){
	if(b>a)a=b;
	return ;
}
void dfs1(int now,int last){
	g[now][0]=g[now][1]=0;
	b[now]=a[now];
	for(int i=0;i<d[now].size();i++){
		int Next=d[now][i].to;
		if(Next==last)continue;
		dfs1(Next,now);
		b[now]+=b[Next];
		if(b[Next]!=0){
			tot+=d[now][i].v*2;
			if(g[Next][0]+d[now][i].v>g[now][1])g[now][1]=g[Next][0]+d[now][i].v;
			if(g[now][1]>g[now][0])swap(g[now][1],g[now][0]);
		}
	}
//	if(d[now].size()==1&&now!=1)g[now][0]=0;
	return ;
}
void dfs2(int now,int last){
	for(int i=0;i<d[now].size();i++){
		int Next=d[now][i].to;
		if(Next==last)continue;
		if(b[Next]!=0){
			if(g[Next][0]+d[now][i].v==g[now][0])MAX(g[Next][1],g[now][1]+d[now][i].v);
			else MAX(g[Next][1],g[now][0]+d[now][i].v);
			if(g[Next][1]>g[Next][0])swap(g[Next][1],g[Next][0]);
		}
		else g[Next][0]=g[now][0]-d[now][i].v;
		dfs2(Next,now);
	}
	return ;
}
signed main(){
	scanf("%lld%lld",&n,&K);
	for(int i=1;i<n;i++){
		scanf("%lld%lld%lld",&x,&y,&z);
		d[x].push_back({y,z});
		d[y].push_back({x,z});
	}
	for(int i=1;i<=K;i++){
		scanf("%lld",&x);
		a[x]=1;
	}
	dfs1(1,1);
	dfs2(1,1);
//	cout<<tot<<endl;
	for(int i=1;i<=n;i++)printf("%lld\n",tot-g[i][0]);
	return 0;
}
例题四 巡逻
代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+5;
struct node{
	int to,v;
};
vector<node>d[maxn];
int vis[maxn],dp[maxn][3],maxx=0,maxxi=0,n,K,x,y,st,en;
void MAX(int &a,int b){
	if(a<b)a=b;
	return ;
}
void dfs1(int now,int last){
	for(int i=0;i<d[now].size();i++){
		int Next=d[now][i].to;
		if(Next==last||vis[Next])continue;
		vis[Next]=vis[now]+d[now][i].v;
		if(vis[Next]>maxx)
			maxx=vis[Next],maxxi=Next;
		dfs1(Next,now);
	}
	return ;
}
void dfs2(int now,int last){
	for(int i=0;i<d[now].size();i++){
		int Next=d[now][i].to;
		if(vis[Next]!=vis[now]-1)continue;
		d[now][i].v=-1;
		dfs2(Next,now);
		return ;
	}
}
void dfs3(int now,int last){
	dp[now][0]=0,dp[now][1]=0;
	for(int i=0;i<d[now].size();i++){
		int Next=d[now][i].to;
		if(Next==last)continue;
		dfs3(Next,now);
		MAX(dp[now][1],dp[Next][0]+d[now][i].v);
		if(dp[now][1]>dp[now][0])swap(dp[now][0],dp[now][1]);
	}
	return ;
}
int main(){
	scanf("%d%d",&n,&K);
	for(int i=1;i<n;i++){
		scanf("%d%d",&x,&y);
		d[x].push_back({y,1});
		d[y].push_back({x,1});
	}
	memset(vis,0,sizeof vis);
	vis[1]=1;
	dfs1(1,1);
	
	st=maxxi;
	maxx=0;
	memset(vis,0,sizeof vis);
	vis[maxxi]=1;
	dfs1(maxxi,maxxi);
	int ans=maxx-1;
	if(K==1){
		printf("%d\n",2*(n-1)-ans+1);
		return 0;
	}
	en=maxxi;
	dfs2(en,en);
	memset(dp,0,sizeof dp);
	dfs3(en,en);
	int ans2=0;
	for(int i=1;i<=n;i++)
		MAX(ans2,dp[i][0]+dp[i][1]);
	printf("%d\n",2*(n-1)-ans-ans2+2);
	return 0;
}
写在最后
 1、换根dp一般使用两次dfs
 2、树形dp的背包先把不优化的打出来,然后看now与next的范围
 3、更新值使用这种方式:
void MAX(int &a,int b){
	if(a<b)a=b;
	return ;
}

 
                
            
         浙公网安备 33010602011771号
浙公网安备 33010602011771号