CF #343 div2 D Babaei and Birthday Cake dp+线段树维护+(离散)

话说 应该也是到挺好做的题:
题意: 可以用一句话概括——–求最大值递增子序列(不是最长,是最大!)
序列长度是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;
}

你可能感兴趣的:(dp)