题意:给定n个圆柱体的半径和高,输入顺序即圆柱体的编号顺序。现在规定,只有编号和体积均大于另一个圆柱体,才能放到另一个圆柱体的体积上面。求能叠加的最大体积是多少。
酝酿了我三天,才理解。自己敲个代码,还超时了,但是把思路记录一下吧,实在不知道哪里超时了。
思路:dp+线段树维护。本质是求递增序列的最大和。设dp[i]表示以编号为i的圆柱体为顶部的圆柱。大问题拆分成子问题:dp[i]=max(dp[j]) + v[i] ,j表示可以放在i号圆柱体下的合法圆柱体,即体积和编号均小于 i 。但是如果直接枚举状态,然后 去暴力搜索合法的 dp[j],会超时,因此,第二层for循环,合法的dp[j]用线段树维护(但是我还是超时了uhnnnnnnn,,无语啊)。先确定要维护的区间,维护区间为v即每个圆柱体的体积,再把体积从小到大排序。再去遍历状态参量 i ,每一次找到 v[i]在体积区间当中所处的位置。然后在体积区间里小于v[i]的部分里找最大的dp[j]。其实一开始我有个地方不理解,就是只维护体积区间,每次去查1至 i-1 区间里的最大的dp[j],但是我觉得编号不一定满足 j<i,事实证明想多了,因为每一次更新v[i]所处位置的dp[i]值时,,是按照编号顺序来更新的,也就是说哪些体积比它小,但是编号比他大的在后面才会更新,当前这步是不会更新的。
本题,线段树的精髓,每当求出一个位置的dp[i]的时候,都去包含此位置的区间,并且由于还是按照输入顺序来更新的,所以不存在体积小,但是序号大的情况。
上个超时代码吧。
#include<iostream> #include<cstdio> #include<algorithm> #include<cmath> using namespace std; typedef long long ll; struct Node { int l,r; int dat; }t[100000*4]; long long dp[100000*4]; long long v[100000*4]; int H[100000*4]; long long ans=0; const double PI=acos(-1.0); int n; void build(int p,int l,int r) { t[p].l=l,t[p].r=r; //int mid=(l+r)>>1; if(l==r){ t[p].dat=0;return;} int mid=(l+r)>>1; build(p*2,1,mid); build(p*2+1,mid+1,r); t[p].dat=max(t[p*2].dat,t[p*2+1].dat); } ll query(int p,int l,int r) { if(l>r) return 0; if(l<=t[p].l&&t[p].r<=r) return t[p].dat; int mid=(t[p].l+t[p].r)>>1; ll val=-1<<30; if(mid>=l) val=max(val,query(p*2,l,r)); if(mid<r) val=max(val,query(p*2+1,l,r)); return val; } void update(int p,int x,int v) { if(t[p].l==t[p].r){t[p].dat=v;return;} int mid=(t[p].l+t[p].r)>>1; if(x<=mid) update(p*2,x,v); else update(p*2+1,x,v); t[p].dat=max(t[p*2].dat,t[p*2+1].dat); } int main() { cin>>n; int r,h; for(int i=1;i<=n;i++) { cin>>r>>h; v[i]=r*r*h; H[i]=v[i]; dp[i]=v[i]; } sort(H+1,H+1+n); build(1,1,n); for(int i=1;i<=n;i++) { int cur=lower_bound(H+1,H+1+n,v[i])-H; dp[i]=max(dp[i],query(1,1,cur-1)+v[i]); update(1,cur,dp[i]); ans=max(ans,dp[i]); } printf("%.15lf",PI*ans); return 0; }