赞
踩
最近两场xie教练的考试都拉到了动态维护多条直线求最值的凸包问题
然后很愉快的一道都做不出来呢
所以学习了一下下,就写了个小博客
学习了
c
+
+
c++
c++,就必少不了
d
p
dp
dp的花式玩法
也就不会错过各种
O
(
1
)
,
O
(
l
o
g
)
O(1),O(log)
O(1),O(log)的优化
前缀和…斜率优化…单调队列…单调栈…
我们看一个
d
p
dp
dp状态转移方程式
d
p
[
i
]
=
m
a
x
{
d
p
[
j
]
+
b
i
∗
a
j
+
a
[
i
]
}
,
j
<
i
dp[i]=max\{dp[j]+b_i*a_j+a[i]\},j<i
dp[i]=max{dp[j]+bi∗aj+a[i]},j<i
一般这种长相,噢不,准确来说,很多
d
p
dp
dp的这种长相,都会跟凸包挂钩
因为出题人不想考那么板的dp送分,就只能搞搞单调让你优化
而我们目前已知的解决凸包就是斜率优化
假设题目保证 a i a_i ai递增
假设
j
<
k
<
i
j<k<i
j<k<i且决策点
j
j
j优于决策点
k
k
k,则有
d
p
[
j
]
+
b
i
∗
a
j
+
a
[
i
]
>
d
p
[
k
]
+
b
i
∗
a
k
+
b
[
i
]
①
dp[j]+b_i*a_j+a[i]>dp[k]+b_i*a_k+b[i]\ \ \ \ \ \ \ ①
dp[j]+bi∗aj+a[i]>dp[k]+bi∗ak+b[i] ①
d
p
[
j
]
−
d
p
[
k
]
>
b
i
∗
(
a
k
−
a
j
)
②
dp[j]-dp[k]>b_i*(a_k-a_j)\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ ②
dp[j]−dp[k]>bi∗(ak−aj) ②题目保证
a
a
a单增,即
a
k
−
a
j
>
0
a_k-a_j>0
ak−aj>0,继续变形得到
d
p
[
j
]
−
d
p
[
k
]
a
k
−
a
j
>
b
i
③
\frac{dp[j]-dp[k]}{a_k-a_j}>b_i\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ ③
ak−ajdp[j]−dp[k]>bi ③
上述过程其实就是斜率优化的推导过程,这种情况下我们仍然可以吃老本——走斜率优化
但是,如果题目不保证 a i a_i ai单调呢??这个时候,斜率优化可以慢走不送了
这个条件去掉后,为什么斜率优化不行了呢??
再回到上面的推导过程,②能推导出③,实际上是把
a
k
−
a
j
a_k-a_j
ak−aj除了过去
也就是说我们是肯定了
a
k
−
a
j
a_k-a_j
ak−aj的符号才敢这样转移
一旦不确定了,大于小于符号就会错乱,无法斜率优化
ps:我刚刚偷换了一下,我把单增变成了题目不保证单调,因为单减也是可以斜率优化的
所以斜率优化是建立在单调的基础上的
此时我们怎么办呢??
李超树 额(⊙﹏⊙)…按xie教练的话就是——懒标记永久化的线段树
明明就是个懒标记永久化,搞不懂为什么要专门取个名字叫李超树 ·····················——xie教练
管他的,反正我们不需要了解,它能有点用才是我们关注的!
本篇博客仅从“单点查询多条直线的极值”入手,因为博主目前只会这一种
只要你能写出
f
[
i
]
=
m
a
x
/
m
i
n
(
f
[
j
]
∗
h
(
i
)
+
g
[
j
]
)
+
t
(
i
)
f[i]=max/min(f[j]*h(i)+g[j])+t(i)
f[i]=max/min(f[j]∗h(i)+g[j])+t(i)
搞那么复杂干什么, 其实就是你能写出一条一次函数的解析式
y
=
k
x
+
b
y=kx+b
y=kx+b
李超树就能硬刚!
维护上凸包和下凸包差不多,我们以维护最大值为例
李超树每个区间记录的是该区间中点 m i d mid mid处的最大/小函数值对应的函数
分两种情况
这三种情况怎么判断呢?就是怎么写呢?
很简单——直线是单调的
我们只需要掌握
l
,
m
i
d
,
r
l,mid,r
l,mid,r三个点的两点函数值就可以了,具体可看模板
每一个区间都存了一条直线,可能相同也可能不同
一个点被多个区间包含,也就会被多条直线包含
所以我们一路上下来遇到的每一个区间都要算一次
x
x
x对应的函数值,取最大值
有可能先遇到的直线的函数值比后遇到的直线的函数值还小,这是有可能的
因为那些区间的函数解析式是根据那些区间的中点
m
i
d
i
mid_i
midi的函数值决定的
谁管你这个小虾皮
举个栗子
这一路上涉及到的四个点的每一条解析式(黑线)都要把
x
x
x带进去算出
y
1
,
y
2
,
y
3
,
y
4
y_1,y_2,y_3,y_4
y1,y2,y3,y4
double calc( node num, int x ) {
return num.k * x + num.b;
}
bool cover( node old, node New, int x ) {
return calc( old, x ) <= calc( New, x );
}
void insert( int num, int l, int r, node New ) {
if( cover( t[num], New, l ) && cover( t[num], New, r ) ) {//新的直线完全覆盖了原区间的最优直线
t[num] = New;
return;
}
if( l == r ) return;
int mid = ( l + r ) >> 1;
if( cover( t[num], New, mid ) )//区间[l,r]维护x=mid的最优值
swap( t[num], New );
//现在这条直线需要继续往下去更新左右儿子(如果这条直线更优)
if( cover( t[num], New, l ) )
insert( num << 1, l, mid, New );
if( cover( t[num], New, r ) )
insert( num << 1 | 1, mid + 1, r, New );
}
这个版本有限制!!!
这个版本有限制!!!
这个版本有限制!!!
也许是我写法问题
此版本查询维护的是直线,也就是说每一条直线都会覆盖到所有的查询点
我们知道
[
l
,
r
]
[l,r]
[l,r]区间维护的函数解析式是当
x
=
m
i
d
x=mid
x=mid时,函数值最大的那条解析式
因为每一条线都能覆盖完,所以每一个区间
[
l
,
r
]
[l,r]
[l,r]都会维护一条直线
于是乎此版本的优化:
x
=
m
i
d
x=mid
x=mid时直接返回,不再寻找左右儿子
在这个前提下,这个优化才有正确性保障
double query( int num, int l, int r, int x ) {
double ans = -1e9;
int mid = ( l + r ) >> 1;
if( x < mid ) ans = query( num << 1, l, mid, x );
if( mid < x ) ans = query( num << 1 | 1, mid + 1, r, x );
return max( ans, calc( t[num], x ) );
}
这一个版本维护的是线段,可能只会覆盖到一部分查询点
对应的插入操作也会发生改变, [ l , r ] [l,r] [l,r]能插入当且仅当 [ l , r ] [l,r] [l,r]被某条直线完全包含,我们要再写一个 m o d i f y modify modify
void modify( int num, int l, int r, int L, int R, node New ) {
if( L > R ) return;
if( L <= l && r <= R ) {
insert( num, l, r, New );
return;
}
int mid = ( l + r ) >> 1;
if( R <= mid ) modify( num << 1, l, mid, L, R, New );
else if( mid < L ) modify( num << 1 | 1, mid + 1, r, L, R, New );
else {
modify( num << 1, l, mid, L, mid, New );
modify( num << 1 | 1, mid + 1, r, mid + 1, R, New );
}
}
也就是说
[
l
,
r
]
[l,r]
[l,r]区间如果没有被一条直线完全覆盖的话,这个区间是没有存直线的
那么当我们继续沿用之前的优化,
x
=
m
i
d
x=mid
x=mid就返回,很可能这段区间压根没有直线解析式
所以我们必须继续询问左儿子或者右儿子
打破砂锅问到底
double query( int num, int l, int r, int x ) {
double ans;
if( l == r ) return calc( t[num], x - 1 );
int mid = ( l + r ) >> 1;
if( x <= mid ) ans = query( num << 1, l, mid, x );
else ans = query( num << 1 | 1, mid + 1, r, x );
return max( ans, calc( t[num], x - 1 ) );
}
直线也可看作一条无限长的线段,因此此版本适用范围广
如果不是很理解,下面两道例题就能很好的辨析出来,┏ (゜ω゜)=☞
线段游戏的样例就会发现查询的写法不同会有问题,可以分布调试看看
每一个项目看作一条直线,而某一天就是查询点
依题意可得,每一个项目可以覆盖每一天,也就是直线覆盖所有点情况
两种查询模板都可以直接上
#include <cstdio> #include <iostream> using namespace std; #define maxn 50005 struct node { double k, b; node(){} node( double K, double B ) { k = K, b = B; } }t[maxn << 2]; int n; double calc( node num, int x ) { return num.k * x + num.b; } bool cover( node old, node New, int x ) { return calc( old, x - 1 ) <= calc( New, x - 1 ); //题目原因 符合要求的x实际上是需要-1才算得出正确收益 } void insert( int num, int l, int r, node New ) { if( cover( t[num], New, l ) && cover( t[num], New, r ) ) { t[num] = New; return; } if( l == r ) return; int mid = ( l + r ) >> 1; if( cover( t[num], New, mid ) ) swap( t[num], New ); if( cover( t[num], New, l ) ) insert( num << 1, l, mid, New ); if( cover( t[num], New, r ) ) insert( num << 1 | 1, mid + 1, r, New ); } double query( int num, int l, int r, int x ) { double ans = -1e9; int mid = ( l + r ) >> 1; if( x < mid ) ans = query( num << 1, l, mid, x ); if( mid < x ) ans = query( num << 1 | 1, mid + 1, r, x ); return max( ans, calc( t[num], x - 1 ) ); } /* 已测试,此写法依然可过 double query( int num, int l, int r, int x ) { double ans; if( l == r ) return calc( t[num], x - 1 ); int mid = ( l + r ) >> 1; if( x <= mid ) ans = query( num << 1, l, mid, x ); else ans = query( num << 1 | 1, mid + 1, r, x ); return max( ans, calc( t[num], x - 1 ) ); } */ int main() { scanf( "%d", &n ); while( n -- ) { char opt[10]; int k, b, t; scanf( "%s", opt ); if( opt[0] == 'Q' ) { scanf( "%d", &t ); printf( "%d\n", int( query( 1, 1, maxn, t ) / 100 ) ); } else { double k, b; scanf( "%lf %lf", &b, &k ); insert( 1, 1, maxn, node( k, b ) ); } } return 0; }
这道题就很特别了,我们维护的不再是一条直线,而是一条线段
这条线段只能覆盖有限的查询点,而不能覆盖的点我们是不能进行求解的
#include <cstdio> #include <iostream> using namespace std; #define maxn 100000 #define inf 0x7f7f7f7f struct node { double k, b; node(){} node( double K, double B ) { k = K, b = B; } }t[( maxn + 5 ) << 2]; int n, m; double calc( node num, int x ) { return num.k * x + num.b; } bool cover( node old, node New, int x ) { return calc( old, x ) < calc( New, x ); } void insert( int num, int l, int r, node New ) { if( cover( t[num], New, l ) && cover( t[num], New, r ) ) { t[num] = New; return; } if( l == r ) return; int mid = ( l + r ) >> 1; if( cover( t[num], New, mid ) ) swap( t[num], New ); if( cover( t[num], New, l ) ) insert( num << 1, l, mid, New ); if( cover( t[num], New, r ) ) insert( num << 1 | 1, mid + 1, r, New ); } void modify( int num, int l, int r, int L, int R, node New ) { if( L > R ) return; if( l == L && R == r ) { insert( num, l, r, New ); return; } int mid = ( l + r ) >> 1; if( R <= mid ) modify( num << 1, l, mid, L, R, New ); else if( mid < L ) modify( num << 1 | 1, mid + 1, r, L, R, New ); else { modify( num << 1, l, mid, L, mid, New ); modify( num << 1 | 1, mid + 1, r, mid + 1, R, New ); } } double query( int num, int l, int r, int x ) { if( l == r ) return calc( t[num], x ); double ans; int mid = ( l + r ) >> 1; if( x <= mid ) ans = query( num << 1, l, mid, x ); else ans = query( num << 1 | 1, mid + 1, r, x ); return max( ans, calc( t[num], x ) ); /* 这种写法是不对的,原因上面已经分析过了...... double ans = -inf; int mid = ( l + r ) >> 1; if( x < mid ) ans = query( num << 1, l, mid, x ); if( mid < x ) ans = query( num << 1 | 1, mid + 1, r, x ); return max( ans, calc( t[num], x ) ); */ } void build( int num, int l, int r ) { t[num].k = 0, t[num].b = -inf; if( l == r ) return; int mid = ( l + r ) >> 1; build( num << 1, l, mid ), build( num << 1 | 1, mid + 1, r ); } int main() { scanf( "%d %d", &n, &m ); build( 1, 1, maxn ); int x0, x1, y1, x2, y2, opt; double k, b; for( int i = 1, x1, y1, x2, y2;i <= n;i ++ ) { scanf( "%d %d %d %d", &x1, &y1, &x2, &y2 ); if( x1 == x2 ) k = 0, b = max( y2, y1 ); else k = ( y2 - y1 ) * 1.0 / ( x2 - x1 ), b = y1 - k * x1; modify( 1, 1, maxn, max( 1, min( x1, x2 ) ), min( maxn, max( x1, x2 ) ), node( k, b ) ); } while( m -- ) { scanf( "%d", &opt ); if( opt ) { scanf( "%d", &x0 ); double ans = query( 1, 1, maxn, x0 ); printf( "%.6f\n", ( ans <= -inf ? 0 : ans ) ); } else { scanf( "%d %d %d %d", &x1, &y1, &x2, &y2 ); if( x1 == x2 ) k = 0, b = max( y2, y1 ); else k = ( y2 - y1 ) * 1.0 / ( x2 - x1 ), b = y1 - k * x1; modify( 1, 1, maxn, max( 1, min( x1, x2 ) ), min( maxn, max( x1, x2 ) ), node( k, b ) ); } } return 0; }
有一坨不像我
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。