1. 程式人生 > 實用技巧 >洛谷 P4198 樓房重建

洛谷 P4198 樓房重建

思路

此題可轉化為以下模型

給定序列\(a[1...n]\),支援單點修改,每次求區間單調棧大小

\(n,Q\le 10^5\)

區間單調棧是什麼呢?對於一個區間,建立一個棧,首先將第一個元素入棧,從左往右掃,如果當前元素大於等於棧頂元素,就將其入棧,由此形成的棧即為單調不減的區間單調棧。

轉化一下,其實就是求區間內滿足\(a[i]=\max\limits_{j=1}^ia[j]\)\(a[i]\)的個數。

一個自然的想法是維護單調棧的大小\(siz\),那麼如何去進行區間的合併呢?

合併兩個子區間時,假設左子區間為\(L\),右子區間為\(R\),考慮合併之後的單調棧的組成部分:

  • 第一部分:\(L\)

    的單調棧

    因為單調棧是從左往右做的,所以\(L\)的單調棧必然是大區間單調棧的一部分

  • 剩餘部分

    設出函式\(calc(now,pre)\)\(now\)表示當前節點,\(pre\)表示當前單調棧的棧頂,\(calc\)函式計算剩餘部分的單調棧的大小

總的單調棧大小\(siz\)就是\(L_{siz}+calc(R,L_{max})\)

calc的實現

現在有\(calc(now,pre)\)\(l\)表示\(now\)的左子樹,\(r\)表示\(now\)的右子樹

  • 如果\(pre>l_{max}\),說明整個左子區間都不用考慮了,此時答案就變成了\(calc(r,pre)\)
  • 如果\(pre\le l_{max}\),此時\(l\)是有貢獻的,他對\(siz\)的貢獻就是\(calc(l,pre)\),右子樹的貢獻為\(calc(r,l_{max})\),總貢獻就是\(calc(l,pre)+calc(r,l_{max})\)

至此\(calc\)就推完了,但是我們發現如果僅僅是這樣的話,在最壞的情況下,複雜度會爆炸,那麼怎麼優化呢?

觀察\(calc(r,l_{max})\),發現它就等於\(siz-l_{siz}\),所以第二種情況就可以變成\(calc(l,pre)+siz-l_{siz}\),其中\(siz\)都是可以處理好的

這樣我們就可以在\(O(\log n)\)

的時間裡完成一次合併

總時間複雜度\(O(Q\log^2 n)\)

程式碼

/*
Author:loceaner
*/
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define lson rt << 1
#define rson rt << 1 | 1
using namespace std;

const int A = 1e5 + 11;
const int B = 1e6 + 11;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;

inline int read() {
  char c = getchar();
  int x = 0, f = 1;
  for ( ; !isdigit(c); c = getchar()) if (c == '-') f = -1;
  for ( ; isdigit(c); c = getchar()) x = x * 10 + (c ^ 48);
  return x * f;
}

double val;
int n, m, ans, pos;
struct node { double maxn; int siz; } a[A << 2];

void build(int rt, int l, int r) {
  if (l == r) return;
  int mid = (l + r) >> 1;
  build(lson, l, mid), build(rson, mid + 1, r);
}

inline int calc(int rt, int l, int r, double h) {
  if (l == r) return a[rt].maxn > h;
  int mid = (l + r) >> 1;
  if (a[lson].maxn <= h) return calc(rson, mid + 1, r, h);
  return calc(lson, l, mid, h) + a[rt].siz - a[lson].siz;
}

inline void update(int rt, int l, int r) {
  if (l == r) {
    a[rt].maxn = val, a[rt].siz = 1;
    return;
  }
  int mid = (l + r) >> 1;
  if (pos <= mid) update(lson, l, mid);
  else update(rson, mid + 1, r);
  a[rt].maxn = max(a[lson].maxn, a[rson].maxn);
  a[rt].siz = a[lson].siz + calc(rson, mid + 1, r, a[lson].maxn);
}

int main() {
  n = read(), m = read();
  build(1, 1, n);
  while (m--) {
    int x = read(), y = read();
    pos = x, val = (double) y / x;
    update(1, 1, n);
    cout << a[1].siz << '\n';
  }
}