确切地说……
并不算是收了自己的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;
}