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)都初始化为无穷小,表示没有计算过。

#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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值