话说 应该也是到挺好做的题:
题意: 可以用一句话概括——–求最大值递增子序列(不是最长,是最大!)
序列长度是100000,意味着普通的n*n dp 是无法通过的。
好,现在来回想一下最长递增子序列的dp算法:
这个简单dp应该不难理解,就是对于每一个a[i],找到在i前面的j,既有a[i]>a[j]
又有 res[j]是最大值(也可以是最长的长度)!
__int64 LIS(){
__int64 i,j,s,maxnum,res[502];
res[1]=a[1];
for(i=2;i<=n;i++){
maxnum=0;
for(j=1;j<i;j++){
if(a[i]>a[j] && res[j]>maxnum) //这一步实际上是取 区间[1,i-1]中的,符合a[i]>a[j]的最大值
maxnum=res[j]; //
}
res[i]=maxnum+a[i];
}
s=0;
for(i=1;i<=n;i++)
if(res[i]>s)
s=res[i];
//printf("%d\n",s);
return s;
}
那么我们可以知道在区间里面找最大值,显然可以用线段树进行优化,现在的问题就是该选取什么来维护呢?或者说,线段树里面该放什么东西呢?
因为这里的体积特别大,取一个这么大的体积来作为区间更新线段树肯定是不现实的,所以我们要把体积离散化,容易想到:我们可以把体积排序,然后用体积的相对顺序进行表示,以及线段树的更新,查询。
然后 对于每一个v[i],可以用lower_bound 找到其体积的相对位置num[i],即离散化后的值。
于是我们就可以建树:1-n, 而数列中的每一个数自然是num[i]而不是v[i] (离散化),所以处理起来就可以十分简单,
for(int i=1;i<=n;i++) (这个for循环满足 i>j,也就是i只对数列中前面的数进行查询)
对于每一个i,先找到num[i],然后查找区间[1,num[i]-1] 的最大值,然后加上v[i],就可以了。最后更新维护下线段树的最大值。
#include<iostream>
#include<stdio.h>
#include<string.h>
#include<math.h>
#include<algorithm>
#include<stdlib.h>
#include<queue>
#include<stack>
#include<map>
#include<vector>
#define mem(a) memset(a,0,sizeof(a))
#define pfn printf("\n")
#define sf scanf
#define pf printf
#define fr(i,n) for(int i=0;i<n;i++)
#define INF 0x7fffffff //INT_MAX
#define inf 0x3f3f3f3f //
const double PI = acos(-1.0);
const double e = exp(1.0);
template<class T> T gcd(T a, T b) { return b ? gcd(b, a % b) : a; }
template<class T> T lcm(T a, T b) { return a / gcd(a, b) * b; }
using namespace std;
#define lson l , mid , rt << 1
#define rson mid + 1 , r , rt << 1 | 1
const int maxn=100005;
__int64 v[maxn],f[maxn];
int n;
int num[maxn];
struct node{
__int64 l,r;
__int64 m;
}sum[maxn<<2];
//__int64 sum[maxn*2]; //将线段树的结构体替换为数组 MAXN*4;
void push_up(__int64 rt){
sum[rt].m = max(sum[rt<<1].m , sum[rt<<1|1].m);
}
void build(__int64 l,__int64 r,__int64 rt){
sum[rt].m=0;
sum[rt].l = l;
sum[rt].r = r;
if(l==r)
return;
int mid=(l+r) >> 1;
build(l, mid, rt*2);
build(mid+1, r, rt*2+1);
}
void update(__int64 p,__int64 num,__int64 rt){
if(sum[rt].l==sum[rt].r){ //l=r=p
sum[rt].m=max(sum[rt].m,num);
return;
}
__int64 mid=(sum[rt].l+sum[rt].r)>>1;
if(p<=mid) update(p,num,rt*2);
else update(p,num,rt*2+1);
push_up(rt); //因为 ,这一步会等待前面两步的update更新完,实际上前两步就已经有很多次pushup了
}
__int64 query(__int64 L,__int64 R,__int64 rt){ //查找区间的最大值
if(L<=sum[rt].l && sum[rt].r<=R ){
return sum[rt].m;
}
__int64 mid = (sum[rt].l+sum[rt].r)>>1;
__int64 temp=0;
if(L <= mid) temp=max(temp,query(L,R,rt*2));
if(R>mid) temp=max(temp,query(L,R,rt*2+1));
return temp;
}
// 这个简单dp应该不难理解,就是对于每一个a[i],找到在i前面的j,既有a[i]>a[j]
//又有 res[j]是最大值(也可以是最长的长度)!
int main(){
//freopen("1.txt","r",stdin);
while(~scanf("%d",&n)){
build(1,n,1);
for(int i=1;i<=n;i++){
int r,h;
scanf("%d %d",&r,&h);
v[i]=(__int64)r*r*h;
f[i]=v[i];
}
sort(f+1,f+n+1);
for(int i=1;i<=n;i++){ //这一步就是离散化,因为体积很大,我们将其编号离散.
num[i]=lower_bound(f,f+n,v[i])-f ; //
// printf("%d\n",num[i]);
}
__int64 ans=0;
for(int i=1;i<=n;i++){ //满足序号是递增 :i>j
__int64 flag=0;
if(num[i]==1)
flag=v[i];
else
flag=query(1,num[i]-1,1)+v[i]; //满足 a[i]>a[j],且加的是最大值.
// 第一次写成 nun[i-1]了,这就很sb了
//printf("dp=%d\n",dp[i]);
update(num[i],flag,1);
ans=max(ans,flag);
}
printf("%.10f\n",(double)ans*PI);
}
return 0;
}
最后还有一个疑惑:
刚开始是用数组写的线段树,然而 一直超内存,巨巨们都说是我自己写错了,但是还没有找出错,哪位巨巨知道我写错了,麻烦告诉一下,下面贴一下我用数组开线段树写的代码
#include<iostream>
#include<stdio.h>
#include<string.h>
#include<math.h>
#include<algorithm>
#include<stdlib.h>
#include<queue>
#include<stack>
#include<map>
#include<vector>
#define mem(a) memset(a,0,sizeof(a))
#define pfn printf("\n")
#define sf scanf
#define pf printf
#define fr(i,n) for(int i=0;i<n;i++)
#define INF 0x7fffffff //INT_MAX
#define inf 0x3f3f3f3f //
const double PI = acos(-1.0);
const double e = exp(1.0);
template<class T> T gcd(T a, T b) { return b ? gcd(b, a % b) : a; }
template<class T> T lcm(T a, T b) { return a / gcd(a, b) * b; }
using namespace std;
#define lson l , mid , rt << 1
#define rson mid + 1 , r , rt << 1 | 1
const int maxn=100005;
__int64 v[maxn],f[maxn];
int n;
int num[maxn];
__int64 sum[maxn*2]; //将线段树的结构体替换为数组 MAXN*4;
void push_up(__int64 rt){
sum[rt] = max(sum[rt<<1] , sum[rt<<1|1]);
}
void build(__int64 l,__int64 r,__int64 rt){
sum[rt]=0;
if(l==r)
return;
int mid=(l+r) >> 1;
build(lson);
build(rson);
}
void update(__int64 p,__int64 num,__int64 l,__int64 r,__int64 rt){
if(l==r){ //l=r=p
sum[rt]=max(sum[rt],num);
return;
}
__int64 mid=(l+r)>>1;
if(p<=mid) update(p,num,lson);
else update(p,num,rson);
push_up(rt); //因为 ,这一步会等待前面两步的update更新完,实际上前两步就已经有很多次pushup了
}
__int64 query(__int64 L,__int64 R,__int64 l,__int64 r,__int64 rt){ //查找区间的最大值
if(L<=l && r<=R ){
return sum[rt];
}
__int64 mid = (l+r)>>1;
__int64 temp=0;
if(L <= mid) temp=max(temp,query(L,R,lson));
if(R>mid) temp=max(temp,query(L,R,rson));
return temp;
}
int main(){
//freopen("1.txt","r",stdin);
while(~scanf("%d",&n)){
build(1,n,1);
for(int i=1;i<=n;i++){
int r,h;
scanf("%d %d",&r,&h);
v[i]=r*r*h;
f[i]=v[i];
}
sort(f+1,f+n+1);
for(int i=1;i<=n;i++){ //这一步就是离散化,因为体积很大,我们将其编号离散.
num[i]=lower_bound(f,f+n,v[i])-f ; //
// printf("%d\n",num[i]);
}
__int64 ans=0;
for(int i=1;i<=n;i++){ //满足序号是递增 :i>j
__int64 flag;
if(num[i]==1)
flag=v[i];
else
flag=query(1,num[i]-1,1,n,1)+v[i]; //满足 a[i]>a[j],且加的是最大值.
// 第一次写成 nun[i-1]了,这就很sb了
//printf("dp=%d\n",dp[i]);
update(num[i],flag,1,n,1);
ans=max(ans,flag);
}
printf("%.10f\n",(double)ans*PI);
}
return 0;
}