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

来源:互联网 发布:mac如何转换输入法 编辑:程序博客网 时间:2024/06/07 00:16

确切地说……
并不算是收了自己的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].tousing 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 pairtypedef 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 0