[luogu P4198] 楼房重建(线段树 + 思维)
luogu 樓房重建
- problem
- solution
- code
problem
洛谷鏈接
solution
非常巧妙的一道題,對線段樹的運用很靈活。
顯然這個與原點的連線可以想到將每個點轉化為與原點連線形成的直線斜率。
答案其實就是:從第一個點開始選,后一個斜率比前面大的必須選,否則,即小于等于則必須不選的選擇個數。
區間是固定的,且左邊的一些信息影響右邊,每次還要單點修改,可以聯想到線段樹。
這個線段樹沒有懶標記,自然也沒有下放標記操作。唯一的難點就是在于合并操作的實現。
線段樹區間 [l,r][l,r][l,r] 維護從 lll 開始選,按照答案的規則選出的個數答案。
最終輸出線段樹節點 111 表示 [1,n][1,n][1,n] 區間的答案即可。
顯然,一段區間的第一個一定被選,一段區間中的最大值也一定被選。
而一段區間的最大值會影響后面區間的可選值,所以還要維護一個區間 [l,r][l,r][l,r] 中最大的斜率。
考慮左右兒子具體怎么合并給父親。
首先,左兒子合并上去后做的是開頭部分,所以左兒子的答案一定貢獻。
其次,左兒子一定會選其區間內的斜率最大值,而右兒子的斜率都必須大于這個最大值。
這里,我們采取遞歸實現合并方式,pushup(now,l,r,k) 表示要求線段樹 nownownow 節點代表的區間 [l,r][l,r][l,r] 選擇的斜率必須大于 kkk 的答案。
一開始是要求右兒子選擇整體要大于左兒子的斜率最大值的。
t[now].ans = t[lson].ans + pushup( rson, mid + 1, r, t[lson].Max );然后開始遞歸考慮右兒子的子樹,即具體的 pushup 實現。
-
如果節點內的最大值都不超過要求的斜率,直接返回。
if( t[now].Max <= k ) return 0; -
如果左區間的最大值都不超過要求的斜率,那么直接找右區間即可。
if( t[lson].Max <= k ) return pushup( rson, mid + 1, r, k ); -
否則,就要去找左區間的答案。左區間既然有些要被選,那么就沒有影響右區間的開始,直接統計右區間的答案。
而右區間的答案不是 t[rson].ans 而是 t[now].ans-t[lson].ans。
因為右區間可能有些會被左區間擋住不能選,就只能換一種方法表示了。
return pushup( lson, l, mid, k ) + t[now].ans - t[lson].ans; -
走到葉子節點,就直接判斷是否大于要求的斜率即可。
if( l == r ) return t[now].Max > k; -
trick
可以記錄一下橫坐標為 lll 的點與原點形成直線的斜率。
如果這個斜率就一定大于要求值的話,那么就相當于從 lll 開始按照規則選的個數。
即線段樹上 [l,r][l,r][l,r] 區間維護的信息,那么直接返回就不用再往下操作了。
if( g[l] > k ) return t[now].ans;
code
#include <bits/stdc++.h> using namespace std; #define maxn 100005 struct node { double Max; int ans; }t[maxn << 2]; double g[maxn];#define lson now << 1 #define rson now << 1 | 1 #define mid ( ( l + r ) >> 1 )int pushup( int now, int l, int r, double k ) {if( t[now].Max <= k ) return 0;if( k < g[l] ) return t[now].ans;if( l == r ) return t[now].Max > k;if( t[lson].Max <= k ) return pushup( rson, mid + 1, r, k );else return pushup( lson, l, mid, k ) + t[now].ans - t[lson].ans; }void modify( int now, int l, int r, int x, int y ) {if( l == r ) { t[now].Max = 1.0 * y / x, t[now].ans = 1; return; }if( x <= mid ) modify( lson, l, mid, x, y );else modify( rson, mid + 1, r, x, y );t[now].Max = max( t[lson].Max, t[rson].Max );t[now].ans = t[lson].ans + pushup( rson, mid + 1, r, t[lson].Max ); }int main() {int n, m, x, y;scanf( "%d %d", &n, &m );while( m -- ) {scanf( "%d %d", &x, &y );g[x] = 1.0 * y / x;modify( 1, 1, n, x, y );printf( "%d\n", t[1].ans );} }總結
以上是生活随笔為你收集整理的[luogu P4198] 楼房重建(线段树 + 思维)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 怎么进dns(怎么进dns设置)
- 下一篇: 【无码专区13】最小公倍数(线段树)