luogu P4198 樓房重建——線段樹

weixin_34104341發表於2020-04-07

題目大意:

小A在平面上(0,0)點的位置,第i棟樓房可以用一條連線(i,0)和(i,Hi)的線段表示,其中Hi為第i棟樓房的高度。如果這棟樓房上任何一個高度大於0的點與(0,0)的連線沒有與之前的線段相交,那麼這棟樓房就被認為是可見的。

施工隊的建造總共進行了M天。初始時,所有樓房都還沒有開始建造,它們的高度均為0。在第i天,建築隊將會將橫座標為Xi的房屋的高度變為Yi(高度可以比原來大—修建,也可以比原來小—拆除,甚至可以保持不變—建築隊這天什麼事也沒做)。請你幫小A數數每天在建築隊完工之後,他能看到多少棟樓房?

分析:

顯然可以想到進行斜率處理,通過斜率的單調遞增來求出len。

其實答案就是整個1—n區間中從第一項開始,每一個大於前一項的必選,小於等於前一項的必須不選,所的得到的序列長度。

因為區間是固定的,並且發現一個區間內的答案可以通過兩個子區間用某種方式進行轉移。所以可以考慮到線段樹做法。

線段樹中只需要維護兩個值,一個是區間最大值,還有一個是區間序列長度(按照剛才的理解)的值。

建樹(甚至不用),修改,甚至不用pushdown,一切好說。但是發現pushup不好處理,顯然兩個子區間的值不能直接合並。必須滿足一定關係。

可以發現,區間內的第一項一定在這個序列內,區間最大值也一定在這個序列內。

對於要被pushup的區間,它的兩個子區間已經處理好了,容易知道,左兒子區間內的序列每一項一定都在這個大區間內。(因為前面形態固定,又不能選擇不看到)所以只需要處理右兒子區間和左兒子區間最大值的關係,即可遞迴處理len值。

遞迴要傳入該區間的值必須大於的最小值,設為lx。對於開始進入時,也就是左兒子的最大值。

1.如果l==r,該位置的值大於lx,return1,否則return0;

2.將該區間劈成兩段,設為s1,s2區間。

①如果s1的最大值小於等於lx,那麼s1必然不會對答案產生貢獻,去找s2。即程式碼中: return pushup2(lx,s2,mid+1,r)

②如果s1的最大值大於lx,那麼s2中剩下的在s1,s2組成的原區間中做貢獻的項一定能貢獻到最終答案中。即+l(x)-l(s1),這裡注意,不是 l(s2),因為可能在l(s2)中存在的項,不一定在l(x)這個大區間中出現。所以這兩個值是完全不同的概念。

之後再去尋找s1. 即:return pushup2(lx,s1,l,mid)+l(x)-l(s1);

核心程式碼:

int pushup2(double lx,int x,int l,int r)
{
    if(m(x)<=lx) return 0;//剪枝
    if(a[l]>lx) return l(x);//剪枝
    if(l==r) return a[l]>lx;//
    int s1=x<<1,s2=x<<1|1;
    int mid=(l+r)>>1;
    if(m(s1)<=lx) return pushup2(lx,s2,mid+1,r);//
    else return pushup2(lx,s1,l,mid)+l(x)-l(s1);//
}

詳見程式碼:

#include<bits/stdc++.h>
using namespace std;
const int N=100000+10;
int n,m;
double a[N];
struct node{
    double mx;
    int len;
    #define m(x) t[x].mx
    #define l(x) t[x].len
}t[4*N];
void pushup1(int x)
{
    m(x)=max(m(x<<1),m(x<<1|1));
}
int pushup2(double lx,int x,int l,int r)
{
    if(m(x)<=lx) return 0;
    if(a[l]>lx) return l(x); 
    if(l==r) return a[l]>lx;
    int s1=x<<1,s2=x<<1|1;
    int mid=(l+r)>>1;
    if(m(s1)<=lx) return pushup2(lx,s2,mid+1,r);
    else return pushup2(lx,s1,l,mid)+l(x)-l(s1);
}
void chan(int x,int l,int r,int to,int c)
{
    if(l==r&&l==to)
    {
        m(x)=(double)c/to;
        l(x)=1;
        return ;
    }
    int mid=(l+r)>>1;
    if(to<=mid) chan(x<<1,l,mid,to,c);
    else if(to>mid) chan(x<<1|1,mid+1,r,to,c);
    pushup1(x);
    l(x)=l(x<<1)+pushup2(m(x<<1),x<<1|1,mid+1,r);
}
int main()
{
    scanf("%d%d",&n,&m);
    int x,y;
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d",&x,&y);
        a[x]=(double)y/x;
        chan(1,1,n,x,y);
        printf("%d\n",t[1].len);
    }
    return 0;
}

總結:

1.其實這個題就是把pushup logn化,是pushup一種難度升級版。

2.只要可以想辦法區間合併的問題,都可以嘗試用線段樹解決。雖然有時候一看看不出來。

轉載於:https://www.cnblogs.com/Miracevin/p/9031636.html