偷天换日 树形DP+背包DP

老规矩先上题面链接

偷天换日

思路

首先拿到题我们就会发现这题是一个树形结构
在每一个叶节点有一个类似0/1背包的问题
显然0/1背包比树形结构好跑
由于树形结构的DFS性质,这题的主要框架注定会是记搜
并且,拿到树形结构为问题的时候,我们就可以以子树划分阶段了
然后在每个叶节点及展厅跑一个0/1背包即可

DP设计

依惯例,我们对DP的设计进行一个详解:
1.阶段:
对于某个非叶节点的节点来说:以子树为阶段
对于某个非叶节点来说:以每一幅画为状态
2.状态:
对于时间花销问题:
本题时间范围较小,可以以时间花销为状态进行DP
对于遍历边和进行0/1来说,都是需要时间花销的,
因此以时间为状态可以通用于两部分DP
3.决策:
对于某个非叶节点的节点来说:往哪个方向走就是决策
对于某个非叶节点来说:偷哪幅画就是决策
4.状态转移方程:
树形:f[u][i]=std::max(f[u][i],f[u<<1][i-j-x]+f[u<<1|1][j]);
0/1:f[u][j]=std::max(f[u][j],f[u][j-c[i]]+v[i]);

实现细节

1.针对输入DFS序的问题:
在进行记搜(递归)的过程中实现输入即可
2.针对叶节点进行0/1DP的问题:
注意循环到的成本最小值要加上来去的成本,否则时间的开销会有偏差
3.针对边权的问题:
每条要跑的边都需要跑两次,因此边权乘2
4.针对逃跑时间的问题:
逃跑时间最晚n-1,如果是n代表主人公会和警察在第n时刻恰好相遇

Code

偷天换日
#include <bits/stdc++.h>
int n,m,a,b;
const int o=5700;
int v[o],f[o][o],c[o];
namespace mf{
	int read(){
		int x=1,y=0;
		char ch=getchar();
		while(!isdigit(ch)){
			if(ch=='-'){
				x=-1;
			}
			ch=getchar(); 
		}
		while(isdigit(ch)){
			y=(y<<1)+(y<<3)+(ch&15);
			ch=getchar();
		}
		return x*y;
	}
	void work(int u){
		int x,y;
		x=read();
		y=read();
		x<<=1;
		if(y){
			for(int i=1;i<=y;i++){
				v[i]=read();
				c[i]=read();
			}
			for(int i=1;i<=y;i++){
				for(int j=n;j>=c[i]+x;j--){
					f[u][j]=std::max(f[u][j],f[u][j-c[i]]+v[i]);
				}
			}
		}
		else{
			work(u<<1);
			work(u<<1|1);
			for(int i=x;i<=n;i++){
				for(int j=0;j<=i-x;j++){
					f[u][i]=std::max(f[u][i],f[u<<1][i-j-x]+f[u<<1|1][j]);
				}
			}
		}
	}
	void in(){
		n=read();
		n--;
	}
	void out(){
		printf("%d",f[1][n]);
	}
}
int main(){
	freopen("steal.in","r",stdin);
	freopen("steal.out","w",stdout);
	mf::in();
	mf::work(1);
	mf::out();
	return 0;
}
posted @ 2022-02-12 15:45  2K22  阅读(37)  评论(0)    收藏  举报