技能树 DP

题目链接

题目大意

  • 问题描述

热爱电子娱乐的同学们对于技能树一定不陌生.就是说,要先学习低级的垃圾技能,特定的几个垃圾技能学会了,才能学习更强的技能.比如说,要先学火球术和烈火墙,才能学习地狱烈焰.科技树也是一样.要先研究出电力和内燃机,才能研究工业学.那么,现在我们把问题简化,这是一个技能树(或者科技树).格子上的数,是威力值.要先学会第一排第二个和第三个,才能学会第二排的第二个.每个技能学习的前提都是左上和右上的两个技能.假设现在有一个第一层有N个技能的技能树,而且技能点是有限的,只能学习M个技能,我们想知道最大的威力值之和是多少.
这里写图片描述

  • 输入格式

第一行两个数N和M,如题所述
之后N行,第i行,有n+1-i个数.表示一个技能树.

  • 输出格式

输出一个数,表示最大威力值之和

  • 样例输入

样例1
4 5
1 1 1 1
1 2 1
1 1
1

样例2
3 5
548 592 715
844 602
857

  • 样例输出

样例1
6

样例2
3301

  • 数据范围和提示

对于40%的数据,N<=10
对于100%的数据,N<=50,M<=500,所有数据都在longint之内.

分析

由于原技能树的形状是三角锥形,且学习得到的技能组成的形状呈锯齿状,所以我们可以尝试这样存数据,来显得更加直观。
如下图,蓝色的元素代表学习的技能,红色代表未学习的。
但是我们发现一个问题,这样的储存方法不便于遍历。所以我们可以考虑转换一下。
原问题是求蓝色元素值的之和最大,相同的,可以求红色元素的值之和最小。这样问题就简单了。
那么红色元素应满足怎样的条件,状态转移方程怎么写呢?
(这里由于个人偏好,数据是从上到下、从左到右储存的)
设所有技能的威力总和为TS,left表示未学习的元素个数,即红色元素个数。sum[i][j]表示第i行前j个元素的值之和,f[i][j][k]表示到第i行第j个元素、未学习的技能为k个时,红色元素值之和的最小值。pj表示下一行红色元素的个数。
红色元素满足的条件:
(1)红色元素的总个数为left = n×(n+1)/2 - m
(2)每行红色元素从左端点向右延伸
(3)每行红色元素个数≤下一行的红色元素个数
状态转移方程 f[i][j][k] = min(f[i][j][k], f[i+1][pj][k-j] + sum[i][j])
图1

代码

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 60, M = 510;
typedef long long LL;
int n, m, left;
LL w[N][N], f[N][N][N*N];
int sum[N][N];
LL ans = 0xfffffffffffffff, TS;

int main() {
	scanf("%d%d", &n, &m);
	for(int i = 1; i <= n; i++) 
		for(int j = 1; j <= n-i+1; j++) {
			scanf("%lld", &w[i+j-1][j]);
			TS += w[i+j-1][j];
		}
	left = (n*(n+1)/2) - m;
	for(int i = 1; i <= n; i++) {
		for(int j = 1; j <= i; j++) {
			sum[i][j] = sum[i][j-1] + w[i][j];
		}
	}
	memset(f, 0x7f, sizeof(f));
	for(int i = 1; i <= n; i++)
		f[n][i][i] = sum[n][i];
	for(int i = n-1; i >= 1; i--) {
		for(int j = 1; j <= min(left, i); j++) {
			for(int k = j*(n-i+1); k <= left; k++) { //这里 k = j*(n-i+1) 的意思是此行和下面每行红色元素个数都为j时
				for(int pj = j; pj <= i+1; pj++) {
					f[i][j][k] = min(f[i][j][k], f[i+1][pj][k-j] + sum[i][j]);
				}
			}
		}
	}
	for(int i = 1; i <= n; i++)
		for(int j = 1; j <= i; j++)
			ans = min(ans, f[i][j][left]);
	printf("%lld\n", TS - ans);
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值