Screensaver

你最近安装了一个新的屏幕保护程序,如果你离开键盘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 就是沙土面积了。

#include
#include
#include

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 ; i1 ; i++ ) Intership( i , i+1 , 1 ) ;
}

int main() {
    scanf("%d%d",&n,&m ) ;
    for( i=0 ; i"%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

你可能感兴趣的:(Screensaver)