题意:有几个蛋糕,要求把他们按照这样的方式堆起来:
下面的编号比上面的小;下面的体积比上面的小。
求把这些蛋糕堆起来能得到的最大体积是多少。
思路:
首先,一定要排序。按照编号排还是按照体积排?
题目中输入顺序就是按照编号排序的。先考虑按照编号排序。对于每个i,要找到j小于i,且V(j) < V(i)中总体积最大的j,然后到i的最优解就是j的最优解加上V(i)。
初步看上去,好像是dp。
如果每次找j用扫描的办法,可以解决问题,但是复杂度是n方的。
如果每次不用扫描,用线段树来保存从1到i的最大值,这样复杂度世够了,但是无法保证V(j) < V(i)。
按照编号排序貌似不行,但是感觉就差一点点。
换成按照体积排序。这样,我们每次选择到蛋糕一定能满足V(j) < V(i),而且用线段树很容易求出1到i中的最优解,这样两个条件就都能满足了。
因为题目要求V(j)严格小于V(i),所以排序的时候,对于相等的体积,序号小的要放后面。
官方给出题解的译文:
首先,我们计算每个蛋糕的体积:
vi=π∗hi∗r2i
现在,考虑序列
v1,v2,v3,…,vn
:问题的答案是这个序列中递增子序列的最大和。我们怎么样解决它?首先去掉小数,我们可以定义一个新的数组
a1,a2,a3,…,an,ai=vi/π=hi∗r2i
我们考虑
dpi
是以
ai
结束的序列和的最大值且
dpi=max(ai,maxj<i,aj≤aidp[j]+ai)
这个问题的答案就是:
π∗maxi=1tondp[i]
现在,我们怎么计算
dpi=max(ai,maxj<i,aj≤aidp[j]+ai)
?我们使用一个线段树,这个线段树有两种操作:1.将第i个数更改为v;2.找出1到i中最大的数。
现在,我们将dp与线段树结合寻找答案。
假设
a1,a2,a3,…,an
已经排序好了。我们定义
bi是ai
的位置。现在填充
dpi
,我们找出区间
[1,bi]
中最大的, 设为x,然后将线段树中
bi
个位置设置成
ai+x
。问题的答案就是区间[1,n]中的最大值。
时间复杂度:
O(nlogn)
代码如下:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
typedef long long ll;
const double PI = acos(-1);
struct Cake{
ll V;
int id;
bool operator < (const Cake a) const {
if(V != a.V)
return V<a.V;
return id>a.id;
}
}cake[100005];
ll dp[400005];
int p;ll v;
void update(int l,int r,int o){
if(l == r){
dp[o] = v;return;
}
int m = (l+r)/2;
if(p<=m)update(l,m,o*2);
else update(m+1,r,o*2+1);
dp[o] = max(dp[o*2],dp[o*2+1]);
}
int ql,qr;
ll query(int l,int r,int o){
if(l>=ql && r <= qr)return dp[o];
ll ret = 0;
int mid = (l+r)/2;
if(ql <= mid) ret = max(ret,query(l,mid,o*2));
if(qr > mid) ret = max(ret,query(mid+1,r,o*2+1));
return ret;
}
int main()
{
// freopen("data.txt","r",stdin);
memset(dp,0,sizeof(dp));
int n;
scanf("%d",&n);
for(int i = 1; i<= n; ++i){
ll r,h;
scanf("%lld%lld",&r,&h);
cake[i].V = r * r * h;
cake[i].id = i;
}
sort(cake+1,cake+n+1);
for(int i = 1; i <= n ; ++i ){
ql = 1;qr = cake[i].id;
v = query(1,n,1) + cake[i].V;
p = cake[i].id;
update(1,n,1);
}
ql = 1;qr = n;
ll ans = query(1,n,1) ;
printf("%.6lf\n", ans * PI);
return 0;
}