屏保

题意:
一个有热带鱼的水族馆,水族馆的底端是由沙石形成的供鱼玩耍的地方,沙石的高度可以设置,水位也可以设置。水族馆可以看做是一个二维平面,宽看作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 这个区域,如果有部分沙石在水面以上,这部分形成一个岛屿。对于不同的沙石情况,你想知道被水覆盖区域的面积,即水位以下总面积减去水中沙石的面积。

N(3<=N<=100,000)
询问数M(1<=M<=100,000)
沙石高度H_i(0<=Hi<=1000)

分析:
设ans[h] 为 水面高度为h时水面下沙石的面积。
考虑两个相邻点组成的图形的面积对ans[h]的贡献,设它们的高度分别是X和Y。
hmin(x,y) 时,这个图形对ans[h]的贡献是一个一次函数。
2° min(x,y)< hmax(x,y) 时,这个图形对ans[h]的贡献是一个二次函数。
3°max(x,y) h时,这个图形对ans[h]的贡献是一个常数。
所以我们可以在ans[h]记录答案关于h的二次项系数、一次项系数、常数。用线段树上述三种情况进行区间修改。

#include <cstdio>
#include <algorithm>
using namespace std;

typedef double Do;
const int N = 1e5 + 10;
int h[N];
double pass[N / 100 * 4][3],ans[N][3];
int n,m;

void push(int tot,int l,int r) {
    if (!pass[tot][0] && !pass[tot][1] && !pass[tot][2]) return;
    if (l == r) {
        for (int i = 0;i < 3;i ++) ans[l][i] += pass[tot][i];
    }
    else {
        for (int i = 0;i < 3;i ++) {
            pass[tot << 1][i] += pass[tot][i];
            pass[tot << 1 | 1][i] += pass[tot][i];
        }
    }
    for (int i = 0;i < 3;i ++) pass[tot][i] = 0;
}

void ins(int tot,int l,int r,int l1,int r1,Do a,Do b,Do c) {
    if (l1 > r1) return;
    if (l1 <= l && r1 >= r) {
        pass[tot][2] += a;
        pass[tot][1] += b;
        pass[tot][0] += c;
        push(tot,l,r);
        return;
    }
    push(tot,l,r);
    int mid = (l + r) >> 1;
    if (l1 <= mid) ins(tot << 1,l,mid,l1,r1,a,b,c);
    if (mid < r1) ins(tot << 1 | 1,mid + 1,r,l1,r1,a,b,c);
}

void add(int X,int Y,int t) {
    double x = X,y = Y;
    if (x > y) swap(x,y);
    ins(1,1,1000,1,min(X,Y),0,t,0);
    double z = y - x;
    ins(1,1,1000,min(X,Y) + 1,max(X,Y),-t * 1 / (2 * z),t * y / z,t * (x + z / 2 - y * y / (2 * z)));
    ins(1,1,1000,max(X,Y) + 1,1000,0,0,t * (x + y) / 2);
}

void get(int tot,int l,int r,int pos) {
    push(tot,l,r);
    if (l == r) return;
    int mid = (l + r) >> 1;
    if (pos <= mid) get(tot << 1,l,mid,pos);
    else get(tot << 1 | 1,mid + 1,r,pos);
}

int main() {
    scanf("%d%d",&n,&m);
    for (int i = 1;i <= n;i ++) scanf("%d",&h[i]);
    for (int i = 1;i < n;i ++) add(h[i],h[i + 1],1);
    for (int i = 1;i <= m;i ++) {
        char type;
        scanf(" %c",&type);
        if (type == 'Q') {
            int H;
            scanf("%d",&H);
            Do Ans = (n - 1) * H;
            H = min(H,1000);
            get(1,1,1000,H);
            Do cur = 0,x = 1;
            for (int j = 0;j <= 2;j ++) {
                cur += x * ans[H][j];
                x *= H;
            }               
            printf("%.3lf\n",Ans - cur);
        }
        else {
            int k,H;
            scanf("%d%d",&k,&H);
            k ++;
            if (k < n) add(h[k],h[k + 1],-1),add(H,h[k + 1],1);
            if (k > 1) add(h[k - 1],h[k],-1),add(h[k - 1],H,1);
            h[k] = H;
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值