DP之收了前几天的flag系列【树状数组优化

确切地说……
并不算是收了自己的flag吧……
因为我前两天都在打模拟赛……
所以可以叫做“没有收flag”(强行不收flag)
好的这次来扯一道题叫做:
不是那么显然的数据结构优化dp系列之树状数组。
先来看一道题:
题意:求最长不上升子序列。
f[i] = max{j < i && seq[j] >= seq[i]|f[j]} + 1;
于是,我们有了:
O(n^2)做法。
对于最长上升子序列,我们可以维护一个数组叫做S,它保存的是:
S[i] : 在所有长度为i上升子序列中,结尾的最小值。
例如这个东西:1 2 3 4 5:S[3] = 3。
我们发现,for all i < j ,S[i] < S[j],理由是我们可以很无赖地从一个子序列里找……比如长度为3的肯定由长度为2的转移过来,所以我们可以知道长度为2的尾部肯定比长度为3的尾部小……
知道这个之后,我们知道S[]是一个递增的序列,那么可以这样来想:
我们每次找seq[i]所能取到的最大的f[i],也就意味着在S[]里使得max{S[j] < seq[i]|j}、、、也就是说、、、我们可以二分查找seq[i]在S数组里对应了哪一个合法的位置、、、因为S是递增的、、、
那么对于一个不上升的序列,我们怎么办呢?
我们发现,其实只需要把seq的值改成负数并且略微修改一下二分查找的过程即可,代码后面贴、、、
问题来了:
请问树状数组在什么地方得到了体现呢?(一脸萌萌哒的表情)
这个和树状数组并没有任何关系……只不过我想起来顺带说一下……这个是二分优化,虽然不知道它非常广泛的应用是什么……


以上是我的胡言乱语、、、
下面正式开始进入树状数组的优化话题。
我们注意到,f[i] = max{i > j && seq[i] <= seq[j]|f[j]} + 1;
这个东西吧,它可以这样想:
我们每次都询问在1~seq[i]区间内的最大的f[i]。
机智的小朋友们反应了过来:没错!线段树!
很好,seq[i] <= 1e+9 || seq[i] 为小数。
小朋友们又反应了过来:离散化!
很好,线段树常数太大了,而且不太好写。
小朋友们:(一脸幽怨)树状数组!
先说树状数组的询问操作:
询问1~seq[i]的最大值,好搞。
for(int i = san[seq[i]]; i > 0; i -= i & - i);

san[]表示已经离散化过了。『是不是感觉很方便』
修改:
从s到n的数组都要改。

for(int i = s; i <= n ; i += i & - i);

代码如下:
1.二分优化:

#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <iostream>
#define Rep(i,n) for(int i = 1; i <= n ; i ++)
#define N 100005
#define CLR(a,b) memset(a,b,sizeof(a))
int seq[N],S[N];
using namespace std;
int BinSearch(int l,int r,int p){
    while(l <= r){
        int mid = l + r >> 1;
        if(S[mid] <= p)l = mid + 1;
        else if(S[mid] > p)r = mid - 1;
    }
    return l;
}
int main (){
    int n;
    while(~scanf("%d",&n)){
        int ans = 0;
        Rep(i,n)
            scanf("%d",&seq[i]),seq[i] = - seq[i];
        CLR(S,127);
        Rep(i,n){
            int j = BinSearch(1,n,seq[i]);
        //  printf("**%d %d**\n",j,seq[i]);
            S[j] = seq[i];
            ans = max(ans,j);
        }
        printf("%d\n",ans);
    }

    return 0;
}

2.线段树优化:

有写线段树那个时间树状数组早就敲完了……

3.树状数组优化:

#include<algorithm>
#include<cmath>
#include<cstdio>
#include<cstring>
#define Rep(i,n) for(int i = 1; i <= n ; i ++)
#define RepG(i,x) for(int i = head[x] ;~ i ; i = edge[i].next)
#define Rep_d(i,n) for(int i = n ; i > 0 ; i --)
#define Rep_0(i,n) for(int i = 0 ; i < n ; i ++)
#define RD(i,x,n) for(int i = x; i <= n ; i ++)
#define T_Q(i,n) for(int i = n; i > 0; i -= i & (- i))
#define T_U(i,x,n) for(int i = x; i <= n ; i += i & (- i))
#define CLR(a,b) memset(a,b,sizeof(a))
#define v edge[i].to
using namespace std;
int read(){
    char ch = getchar();
    while(ch < '0' || ch > '9')ch = getchar ();
    int x = 0;
    while(ch >= '0' && ch <= '9')x = 10 * x + ch - '0',ch = getchar ();
    return x;
}
int n;
int f[100005],seq[100005],san[100005],t[100005];
bool cmp(int a,int b){return a > b;}
int Query(int x){
    int ans = 0;
    T_Q(i,x)ans = max(t[i],ans);
    return ans;
}
void Upd(int Up,int s){
    T_U(i,s,n)
        t[i] = max(t[i],Up);
}
int Bin_search(int s){
    int l = 1,r = n;
    while(l < r){
        int mid = l + r >> 1;
        if(san[mid] > s)l = mid + 1;
        else if(san[mid] < s)r = mid - 1;
        else return mid;
    }
    return l;
}
int main()
{
    n = read();
    Rep(i,n)
        seq[i] = read(),san[i] = seq[i];

    sort(san + 1,san + 1 + n,cmp);
    Rep(i,n)
        seq[i] = Bin_search(seq[i]);
    Rep(i,n){
        f[i] = Query(seq[i]) + 1;
        Upd(f[i],seq[i]);
    }
    printf("%d\n",Query(n));
    return 0;
}

来看我今天做的一个Codeforces水题,这个是我写的线段树优化……
相!当!丑!
题意:
简 单 的 DP, 题意大概这样:
按照顺序给你n块圆柱蛋糕的半径和高,蛋糕都可以放在桌子上,其中小的可以放在大的上面,求最大的蛋糕。 (n<= 100000;r,h <= 50000)
数学模型:求所有不下降序列的元素中,和最大的那个序列的和。
f[i]表示强制选i作(托盘)最后一个蛋糕时的最大体积。
有f[i] = max{j < i && V[i] > V[j]|f[j]} + V[i];
也就是每次都要询问:
1~V[i]区间内的最大值!
树状数组优化即可,然而……算了不说了看代码吧。

/*
    http://codeforces.com/contest/629/problem/D
    ID:SingleLyra
    PROG:Codeforces629D
    LANG:C++
    Solution:
        简 单 的 DP, 题意大概这样:
            按照顺序给你n块圆柱蛋糕,蛋糕都可以放在桌子上,其中小的可以放在大的上面,求最大的蛋糕。 (n<= 100000;r,h <= 50000)
            数学模型:求所有不下降序列的元素中,和最大的那个序列的和。
            (然后就被何神秒杀了=,=)
            有dp方程:
                f[i]表示强制选择第i个蛋糕所能形成的当前最大蛋糕。 
                f[i] =  max{j < i && V[j] > V[i] |f[j]} + V[i];
                然后发现了复杂度为n^2,根本过不去……
                然后发现它就是长着一张"我要优化"的脸…… 
                "何神,这道题用什么dp优化啊?" 
                "离散化之后树状数组。" 
                于是我默默地打了个线段树…… 
                2333333……
                注意一点……
                这里的离散化是一种非常高贵的离散化,可以以后来用…… 
*/
                /*
                Rep(i,n)seq[i].X = 1ll * R[i] * R[i] * H[i],seq[i].Y = - i;// 把下标所代表的序号设为负数,所以对于a[i] == a[j](i < j)排序后一定在j之后。 
                sort(seq + 1 ,seq + 1 + n);
                Rep(i,n){
                    int id = -seq[i].Y;  //为什么可以不按照原来顺序的原因:当a[i] > a[j] 时,a[i]必然出现在a[j]之后,所以如果某个数a[i]出现在a[j]之前,那么我们肯定,f[j]不会由f[i]转移得到、、、也就是说,我们询问的是、、、在id之前出现的&&小于seq[id]的最大值、、、         
                    f[id] = Query(1,1,n,1,id) + seq[i].X ;
                    Upd(1,1,n,id,f[id]);
                }
                */
#include<algorithm>
#include<cmath>
#include<cstdio>
#include<cstring>
#define Rep(i,n) for(int i = 1; i <= n ; i ++)
#define RepG(i,x) for(int i = head[x] ;~ i ; i = edge[i].next)
#define Rep_d(i,n) for(int i = n ; i > 0 ; i --)
#define Rep_0(i,n) for(int i = 0 ; i < n ; i ++)
#define RD(i,x,n) for(int i = x; i <= n ; i ++)
#define CLR(a,b) memset(a,b,sizeof(a))
#define X first
#define Y second
#define v edge[i].to
#define pii pair
typedef long double ld;
typedef long long ll;

using namespace std;
int read(){
    char ch = getchar();
    while(ch < '0' || ch > '9')ch = getchar ();
    int x = 0;
    while(ch >= '0' && ch <= '9')x = 10 * x + ch - '0',ch = getchar ();
    return x;
}
const ld pi = M_PI;
int R[100005],H[100005];
ll f[100005];
ll t[100005 << 2];
pii <ll,int>seq[100005];
void Upd(int x,int l,int r,int s,ld U){
    if(l == r){
        t[x] =  U;
        return;
    }
    int mid = l + r >> 1;
    if(mid >= s)Upd(x << 1,l,mid,s,U);
    else Upd(x << 1 | 1,mid + 1,r,s,U);
    t[x] = max(t[x << 1],t[x << 1 | 1]);
}
ll Query(int x,int l,int r,int Ql,int Qr){
    if(l > Qr || r < Ql)return 0;
    if(l >= Ql && r <= Qr)return t[x];
    int mid = l + r >> 1;
    long long ans = 0;
    if(mid >= Ql)ans = max(Query(x << 1,l,mid,Ql,Qr),ans);
    if(mid < Qr)ans = max(Query(x << 1 | 1 ,mid + 1,r,Ql,Qr),ans);
    return ans;
}
int main()
{
    int n = read();
    Rep(i,n)
        R[i] = read(),H[i] = read();
    Rep(i,n)seq[i].X = 1ll * R[i] * R[i] * H[i],seq[i].Y = - i;
    sort(seq + 1 ,seq + 1 + n);
    ll ans = 0;
    Rep(i,n){
        int id = -seq[i].Y;
        f[id] = Query(1,1,n,1,id) + seq[i].X ;
        Upd(1,1,n,id,f[id]);
    }
    ld ans_ = (ld)Query(1,1,n,1,n) * pi;
    printf("%.9f\n",(double) ans_);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值