你最近安装了一个新的屏幕保护程序,如果你离开键盘5 分钟,屏保将会显
示一个有热带鱼的水族馆,水族馆的底端是由沙石形成的供鱼玩耍的地方,沙石
的高度可以设置,水位也可以设置。
水族馆可以看做是一个二维平面,宽看作N-1 列,最左端的横坐标为0,最
右端横坐标为N-1,每个整数横坐标都对应着一个沙石的高度H_i(0<=i<=N-1),
相邻横坐标i 和i+1 之间的沙石可以看做是由(i,H_i)和(i+1,H_i+1)这两个点形成
的线段。
如果水位为h,水覆盖着水族馆底端到y=h 这个区域,如果有部分沙石在水
面以上,这部分形成一个岛屿。
对于不同的沙石情况,你想知道被水覆盖区域的面积,即水位以下总面积减
去水中沙石的面积。
Input
第一行输入两个整数: 水族馆的宽度N(3<=N<=100,000) 和询问数
M(1<=M<=100,000)
第二行输入N 个整数表示一开始水族馆每个横坐标处的沙石高度
Hi(0<=Hi<=1000)
接下来M 行描述以下两种操作:
Q h 询问如果水位为h 时覆盖区域的面积
U i h 把横坐标i(0<=i<=N-1)处的沙石高度设置为h(0<=h<=1000)即Hi=h
Output
对于每个Q 操作,输出覆盖面积,答案保留三位小数。当与答案相差不超过
0.001 时也认为是正确的。
Solution:
思路很新,根据它问什么,可以想到以高度为下标建一棵线段树。答案可以单点查询。
如何维护与计算?
鉴于它插入的是一个个梯形,梯形由下端的长方形与上端的三角形组成。可以看出它下端的沙土面积是线性变化的,三角形那一部分是二次函数变化的,之后改变的只是一个常量。
那么我维护
ax2+by+c
中的
a,b,c
.就可以在线段树上区间修改了。
每次单点取出
a,b,c
然后代入
x
<script type="math/tex" id="MathJax-Element-140">x</script>就是沙土面积了。
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std ;
#define N 100010
int n , m , i , j , k , h[N];
int ax_h = 1000 ;
char s[2] ;
struct node {
double a , b , c ;
double _a,_b,_c ;
}tr[5010] ;
void Effect( int p , int l , int r ) {
tr[p].a+=tr[p]._a , tr[p].b += tr[p]._b , tr[p].c += tr[p]._c ;
if( l!=r ) {
tr[p*2]._a += tr[p]._a ;
tr[p*2]._b += tr[p]._b ;
tr[p*2]._c += tr[p]._c ;
tr[p*2+1]._a += tr[p]._a ;
tr[p*2+1]._b += tr[p]._b ;
tr[p*2+1]._c += tr[p]._c ;
}
tr[p]._a = tr[p]._b = tr[p]._c = 0 ;
}
node nil ;
node Ask( int l , int r , int tar , int p ) {
if( tar==0 ) return nil ;
Effect( p , l , r ) ;
int m = ( l+r ) / 2 ;
if( l==r ) return tr[p] ;
if( tar<=m ) return Ask( l , m , tar , p*2 ) ;
else return Ask( m+1 , r , tar , p*2+1 ) ;
}
void Modify( int l , int r , int _l , int _r , double a , double b , double c , int p ) {
if( _l>_r ) return ;
if( _l==l && _r==r ) {
tr[p]._a+=a , tr[p]._b+=b , tr[p]._c+=c ;
Effect( p , l , r ) ;
return ;
}
int m = ( l+r ) / 2 ;
Effect( p , l , r ) ;
if( _r<=m ) Modify( l , m , _l , _r , a , b , c, p * 2 ) ;
else if( _l>m ) Modify( m+1 , r , _l , _r , a , b , c , p * 2 + 1 ) ;
else Modify( l , m , _l , m , a , b , c, p * 2 ) ,
Modify( m+1 , r , m+1 , _r , a , b , c , p * 2 + 1 ) ;
}
void Intership( int a , int b , int cst ) {
int hax = max( h[a] , h[b] ) ,
hmi = min( h[a] , h[b] ) ;
Modify( 1 , ax_h , 1 , hmi , 0 , cst , 0 , 1 ) ;
double t = hax-hmi ;
Modify( 1 , ax_h , hax+1 , ax_h , 0 , 0 , cst * ( hmi+hax ) / 2.0 , 1 ) ;
if( t==0 ) return ;
// Modify( 1 , ax_h , hmi+1 , hax , cst * ( -1/t ) / 2 , cst * ( hax/t + hmi/t + 1 ) / 2 , cst * ( hmi - hmi*hax/t ) / 2 , 1 ) ;
Modify( 1 , ax_h , hmi+1 , hax , cst * ( -1/t ) / 2 , cst * ( hax / t ) , cst * ( hmi + t / 2 - hax * hax / ( 2 * t ) ) , 1 ) ;
}
void PreBuild() {
for( int i = 0 ; i<n-1 ; i++ ) Intership( i , i+1 , 1 ) ;
}
int main() {
scanf("%d%d",&n,&m ) ;
for( i=0 ; i<n ; i++ ) {
scanf("%d",&h[i] ) ;
}
PreBuild() ;
while( m-- ) {
scanf("%s",s ) ;
if( s[0]=='Q' ) {
int x ;
scanf("%d",&x ) ;
node ans ;
ans = Ask( 1 , ax_h , x , 1 ) ;
double A = ans.a ;
A = A * x + ans.b ;
A = A * x + ans.c ;
printf("%.8lf\n", ( n-1 ) * x - ( x * x * ans.a + x * ans.b + ans.c ) ) ;
} else {
int va , ps ;
scanf("%d%d",&ps,&va ) ;
if( ps!=0 ) Intership( ps-1 , ps , -1 ) ;
if( ps!=n-1 ) Intership( ps , ps+1 , -1 ) ;
h[ps] = va ;
if( ps!=0 ) Intership( ps-1 , ps , 1 ) ;
if( ps!=n-1 ) Intership( ps , ps+1 , 1 ) ;
}
}
}
DebugLog
因为只有单点查询,只用维护叶子节点,不需要合并节点。不然常数太大线段树会TLE