算法笔记_动态规划题

P1156 垃圾陷阱

题意:

深度为 D D D 的坑内有一只牛,每个物品扔下的时间 t t t,高度 h h h,维持的生命的时间 v v v。每个物品都可以用来维持生命或者增加高度,询问最早出坑时间或者最长维持生命的时间

解析:

类似背包问题,垃圾的高度为体积,增加的生命为价值,坑深度为背包的体积。
d p i , j dp_{i,j} dpi,j 为前 i i i 个物品到达高度 j j j 的最长生命时间。对每个物品都有吃或者填两种情况。
吃: d p i , j = m a x ( d p i , j , d p i − 1 , j + a [ i ] . v − ( a [ i ] . t − a [ i − 1 ] . t ) dp_{i,j} = max(dp_{i,j}, dp_{i-1,j} + a[i].v -(a[i].t-a[i-1].t) dpi,j=max(dpi,j,dpi1,j+a[i].v(a[i].ta[i1].t)
填: d p i , j = m a x ( d p i , j , d p i − 1 , j − a [ i ] . h − ( a [ i ] . t − a [ i − 1 ] . t ) dp_{i,j} = max(dp_{i,j}, dp_{i-1,j-a[i].h} -(a[i].t-a[i-1].t) dpi,j=max(dpi,j,dpi1,ja[i].h(a[i].ta[i1].t)
如果 d p i , j ≥ 0 dp_{i,j} \ge 0 dpi,j0 j ≥ D j \ge D jD 则跳出坑。否则为 d p i , 0 的最大值 dp_{i,0}的最大值 dpi,0的最大值
注意到每次转移只与上一个物品的状态有关,可以滚掉一维。

代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 2510;
int dp[2][maxn+50];
int n, H;
struct node{
	int t, f, h;
	bool operator < (const node &b) const{
		return t < b.t;
	}
}a[maxn];
int main(){
	cin >> H >> n;
	for(int i = 1; i <= n; i++){
		cin >> a[i].t >> a[i].f >> a[i].h;
	}
	sort(a+1, a+1+n);
	memset(dp, -1, sizeof(dp));
	dp[0][0] = 10; a[0].t = 0;
	int ans = 10;
	for(int i = 1; i <= n; i++){
		int pre = (i-1)%2;
		int cur = i%2;
		memset(dp[cur], -1, sizeof(dp[cur]));
		for(int j = 0; j <= H+10; j++){
			int pre = (i-1)%2;
			int cur = i%2;
			if(dp[pre][j] >= (a[i].t-a[i-1].t))
				dp[cur][j] = max(dp[cur][j], dp[pre][j]+a[i].f-(a[i].t-a[i-1].t));
			if(j >= a[i].h)
				dp[cur][j] = max(dp[cur][j], dp[pre][j-a[i].h]-(a[i].t-a[i-1].t));
			if(dp[cur][j] >= 0 &&  j >= H){
				cout << a[i].t;
				return 0;
			}
		}
		ans = max(ans, dp[cur][0]);
	}
	cout << ans << endl;
	return 0;
}


P5322 [BJOI2019] 排兵布阵

题意:

没人有 m m m 个士兵,可以任意分配到 n n n 个城堡中,如果在第 i i i 的城堡己方士兵个数严格大于对方士兵数的两倍,获得 i i i 分。每次策略一致,已知其余玩家兵力情况,询问最大的分

解析:

对于城堡 i i i ,如果攻占成功,兵力更少的城堡也能攻占成功,可以快速计算得分。
d p i , j dp_{i,j} dpi,j为前 i i i个城堡派出 j j j士兵的最大的分。
转移方程: d p j = m a x { d p j − a [ i ] [ k ] ∗ 2 − 1 + k ∗ i } dp_j = max \{dp_{j-a[i][k]*2-1}+k*i\} dpj=max{dpja[i][k]21+ki}

代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 2e4+10;
int dp[maxn];
int n, m, s;
int a[110][110]; 
int main(){
	cin >> s >> n >> m;
	for(int i = 1; i <= s; i++)
		for(int j = 1; j <= n; j++)
			cin >> a[j][i];
	for(int i = 1; i <= n; i++)
		sort(a[i]+1, a[i]+1+s);
	for(int i = 1; i <= n; i++)
		for(int j = m; j >= 0; j--)
			for(int k = 1; k <= s; k++){
				if(j >= a[i][k]*2+1)
					dp[j] = max(dp[j], dp[j-a[i][k]*2-1]+k*i);
			} 
	int ans = 0;
	for(int i = 1; i <= m; i++)
		ans = max(ans, dp[i]); 
	cout << ans << endl;
	return 0;
}

P2466 [SDOI2008] Sue 的小球

题意:

二维平面上有许多点,一个人在原点,只能沿x轴运动。当运动到某个小球下方的时候,获得分数为当前小球纵坐标y的值。小球 i i i每秒下落 v i v_i vi个单位,询问收集所有球最高得分。

解析:

当前决策会对未来的费用产生影响。费用提前计算的思想,当前决策要考虑对未来的影响,从而进行状态的转移。
区间dp。 d p i , j , k dp_{i,j,k} dpi,j,k表示收集区间 [ l , r ] [l,r] [l,r]的球并且在左侧或者右侧的最高得分。对未来的影响是当前移动的时间与未收集小球速度和的乘积。利用前缀和,快速查询小球的速度和。

代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e3+10;
const ll INF = 0x3f3f3f3f3f3f3f3f;
ll sumv[maxn];
ll f[maxn][maxn][2];
struct node{
	ll x, y, v;
	bool operator < (const node &b) const{
		return x < b.x;
	}
}a[maxn];
ll n, x0, pos;
int main(){
	cin >> n >> x0;
	for(int i = 2; i <= n+1; i++)
		cin >> a[i].x;
	for(int i = 2; i <= n+1; i++)
		cin >> a[i].y;
	for(int i = 2; i <= n+1; i++)
		cin >> a[i].v;
	a[1] = {x0, 0, 0};
	sort(a+1, a+n+2);
	for(int i = 1; i <= n+1; i++){
		sumv[i] = sumv[i-1]+a[i].v;
		if(a[i].x == x0 && a[i].y == 0 && a[i].v == 0)
			pos = i;	
	}
	memset(f, -INF, sizeof(f));
	f[pos][pos][0] = f[pos][pos][1] = 0;
	for(int len = 1; len <= n+1; len++){
		for(int i = 1; i+len <= n+1; i++){
			int j = i+len;
			f[i][j][0] = a[i].y + max(f[i+1][j][0]-(a[i+1].x-a[i].x)*(sumv[n+1]-sumv[j]+sumv[i]),
										f[i+1][j][1]-(a[j].x-a[i].x)*(sumv[n+1]-sumv[j]+sumv[i]));
			f[i][j][1] = a[j].y + max(f[i][j-1][0]-(a[j].x-a[i].x)*(sumv[n+1]+sumv[i-1]-sumv[j-1]),
										f[i][j-1][1]-(a[j].x-a[j-1].x)*(sumv[n+1]+sumv[i-1]-sumv[j-1]))	;						
		}
	}
	double ans = max(f[1][n+1][1], f[1][n+1][0]);
	ans /= 1000;
	cout << setprecision(3) << fixed << ans << endl; 
} 

P3698 [CQOI2017]小Q的棋盘

题意:

给定一棵 n n n节点的树,从根节点出发,走 m m m步,最多经过多少节点。节点可以重复经过但不重复计数。

解析:

对于以 u u u为根的子树有两种可能,在 u u u的子树中走一圈最后回到 u u u,在 u u u的子树绕圈后向某个子树一直走
f u , j , k f_{u, j, k} fu,j,k为从 u u u j j j步是否回头到达的最大节点数。不回头为0,回头为1
f u , j , 1 f_{u, j, 1} fu,j,1只能由 f v , j − 2 , 1 f_{v, j-2, 1} fv,j2,1转移而来: f u , j , 1 = m a x { f v , k , 1 + f u , j − k − 2 , 1 } f_{u, j, 1} = max\{ f{v, k, 1}+f_{u, j-k-2, 1}\} fu,j,1=max{fv,k,1+fu,jk2,1}
f u , j , 0 f_{u, j, 0} fu,j,0,枚举节点 v v v时,有两种可能,在 v v v一直走,在 v v v之前的节点一直走。
v v v一直走: f u , j , 0 = m a x { f u , j − k − 1 , 0 + f v , k , 0 } f_{u, j, 0} = max\{ f_{u, j-k-1, 0}+f_{v, k, 0}\} fu,j,0=max{fu,jk1,0+fv,k,0}
v v v之前一直走: f u , j , 0 = m a x { f u , j − k − 2 , 0 + f v , k , 1 } f_{u,j,0} = max\{ f_{u, j-k-2, 0}+f_{v, k, 1}\} fu,j,0=max{fu,jk2,0+fv,k,1}

代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e2+10;
int f[maxn][maxn][2];
int head[maxn], tot;
struct edge{
	int to, nxt;
}e[maxn<<1];
int n, m;
void add(int a, int b){
	e[++tot].nxt = head[a];
	e[tot].to = b;
	head[a] = tot;
}
void dfs(int u, int fa){
	for(int i = head[u]; i; i = e[i].nxt){
		int v = e[i].to;
		if(v == fa)
			continue;
		dfs(v, u);
	}
	f[u][0][0] = f[u][0][1] = 1;
	for(int i = head[u]; i; i = e[i].nxt){
		int v = e[i].to;
		if(v == fa)
			continue;
		for(int i = m; i >= 1; i--){
			for(int j = 0; j < i; j++){
				if(i >= j+2){
					f[u][i][1] = max(f[u][i][1], f[u][i-j-2][1]+f[v][j][1]);
					f[u][i][0] = max(f[u][i][0], f[u][i-j-2][0]+f[v][j][1]);
				}
				f[u][i][0] = max(f[u][i][0], f[u][i-j-1][1]+f[v][j][0]);
			}
		}
	}
}
int main(){
	cin >> n >> m;
	for(int i = 1; i <= n-1; i++){
		int a, b;
		cin >> a >> b;
		a++, b++;
		add(a, b); add(b, a);
	}
	dfs(1, 0);
	int ans = 0;
	for(int i = 1; i <= m; i++)
		ans = max(ans, max(f[1][i][0], f[1][i][1]));
	cout << ans << endl;
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值