2015编程之美初赛第一场 B 建造金字塔


时间限制: 4000ms
单点时限: 2000ms
内存限制: 256MB

描述

在二次元中,金字塔是一个底边在x轴上的等腰直角三角形。

你是二次元世界的一个建筑承包商。现在有N个建造订单,每个订单有一个收益w,即建造此金字塔可获得w的收益。对每个订单可以选择建造或不建造。

建造一个金字塔的成本是金字塔的面积,如果两个或多个金字塔有重叠面积,则建造这些金字塔时重叠部份仅需建造一次。

建造一组金字塔的总利润是收益总和扣除成本。现给出这些订单,请求出最大利润。

输入

输入数据第一行为一个整数T,表示数据组数。

每组数据第一行为一个整数N,表示订单数目。

接下来N行,每行三个整数x, y, w,表示一个订单。(x, y)表示建造出的金字塔的顶点,w表示收益。

输出

对于每组数据输出一行"Case #X: Y",X表示数据编号(从1开始),Y表示最大利润,四舍五入到小数点后两位。

数据范围

1 ≤ T ≤ 20

0 ≤ w ≤ 107

小数据

1 ≤ N ≤ 20

0 ≤ x, y ≤ 20

大数据

1 ≤ N ≤ 1000

0 ≤ x, y ≤ 1000


样例输入
3
2
2 2 3
6 2 5
3
1 1 1
2 2 3
3 3 5
3
1 1 1
2 2 3
3 3 6
样例输出
Case #1: 1.00
Case #2: 0.00
Case #3: 1.00

2.解题思路:本题利用区间dp解决。本题要求这些订单中的最大收益。首先可以知道,我们只需要关心每个三角形的右边界点即可。这样才能包含一个完整的三角形。因此,对于所有的三角形,用状态(l,r,w)来描述它。

接下来,定义d(j)表示区间[0,j]上收益的最大值。事先我们要统计出最大边界lim,这样j的变化范围就是0≤j≤lim。下面考虑如何寻找状态转移方程。

(1)当j≥x[i].r时,表示第i个三角形完全包括在[0,j]之间,取建造它与不建造它收益的较大者。即d(j)=max{d(j),d(j)+x[i].w};

(2)当不满足(1)时但j≥x[i].l时,我们关注的是x[i].r处的收益最大值,画图后容易知道,收益增加值是建造第i个金字塔的收益w减去多建设的面积,即S(x[i].l,x[i].r)-S(x[i].l,j)。即得到如下状态转移方程:d(x[i].r)=max{d(x[i].r),d(j)+x[i].w-S(x[i].l,x[i].r)+S(x[i].l,x[i].r)};

(3)当前两个均不满足时,状态转移方程其实和(2)类似,即d(x[i].r)=max{d(x[i].r),d(j)+x[i].w-S(x[i].l,x[i].r)};

最后,不要忘记一种特殊情况:只建造第i个金字塔时候的收益,因此最后还要取上述计算出的收益和只建造第i个金字塔收益的较大者。

当所有区间的收益最大值计算完毕后,答案就是他们中的最大值。注意事先要把d(j)都初始化为无穷小,表示没有计算过。

3.代码:

#define _CRT_SECURE_NO_WARNINGS 
#include<iostream>
#include<algorithm>
#include<string>
#include<sstream>
#include<set>
#include<vector>
#include<stack>
#include<map>
#include<queue>
#include<deque>
#include<cstdlib>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<ctime>
#include<functional>
using namespace std;

double dp[2100];//dp[j]表示[0,j]范围内的最大收益
struct atom{
	int l, r, w;//每个三角形的左边界,右边界,收益
}x[1100];
int n;
int compare(atom k1, atom k2){//按照左边界由小到大排序
	return k1.l<k2.l;
}
double cal(int k){//长度为k的底边的三角形面积
	return k*k / 4.0;
}
double solve()
{
	scanf("%d", &n); int lim = 0;//lim表示最大的右边界
	for (int i = 1; i <= n; i++){
		int k1, k2, k3; scanf("%d%d%d", &k1, &k2, &k3);
		x[i] = atom{ k1 - k2, k1 + k2, k3 }; lim = max(lim, k1 + k2);
	}
	sort(x + 1, x + n + 1, compare);//按照左边界由小大到
	for (int i = 0; i <= lim; i++) dp[i] = -1e18;//初始化为无穷小
	for (int i = 1; i <= n; i++)
	{
		for (int j = lim; j >= 0; j--)
		if (j >= x[i].r) dp[j] = max(dp[j], dp[j] + x[i].w);//当j大于等于右边界时候,取建设i金字塔和不建设的最大值
		else if (j >= x[i].l) dp[x[i].r] = max(dp[x[i].r], dp[j] - cal(x[i].r - x[i].l) + cal(j - x[i].l) + x[i].w);//计算净成本,然后w减去净成本就是多出来的收益
		else dp[x[i].r] = max(dp[x[i].r], dp[j] - cal(x[i].r - x[i].l) + x[i].w);//多出来的收益就是第i个金字塔的收益w减去它的面积
		dp[x[i].r] = max(dp[x[i].r], x[i].w - cal(x[i].r - x[i].l));//最后再取只建造自己时候的较大者
	}
	double ans = 0;
	for (int i = 0; i <= lim; i++) ans = max(ans, dp[i]);//取所有范围中的最大者
	return ans;
}
int main(){
	//freopen("t.txt", "r", stdin);
	int t; scanf("%d", &t);
	for (int i = 1; i <= t; i++){
		printf("Case #%d: %.2lf\n", i, solve());
	}
	return 0;
}

你可能感兴趣的:(编程之美,区间DP)