在二次元中,金字塔是一个底边在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; }