【題解】 CF1400E Clear the Multiset 笛卡爾樹+貪心+dp
阿新 • • 發佈:2020-09-22
Legend
Link \(\textrm{to Codeforces}\)。
給定長度 \(n\ (1 \le n \le 5000)\) 的自然數陣列,你每次可以進行如下操作:
- 給一個區間的數 -1,前提是這個區間沒有 0;
- 給某一個位置上的數減去任意正整數,但操作後值不能小於 0。
求出使得所有數字變成 0 的最少操作次數。
Editorial
不是很理解為什麼 \(O(n)\) 的題目要出成 \(n=5000\)。
如果只有操作 \(1\),那麼就是鋪設道路/積木大賽。不過根據這兩個經典題我們可以知道:如果要用操作 \(1\),一定會貪心地把一個數字減成 \(0\)。
那麼這個不幸被減成 \(0\) 的數字是什麼呢?顯然是區間最小值。
\(O(n^2)\) 做法呼之欲出:每次要麼區間全部都用操作 2 幹掉,要麼先把最小值減掉遞迴成若干個子區間。
用可以區間查詢最小值的資料結構隨便維護維護就可以做到 \(O(n \log n)\) 了。
而這個維護最小值的資料結構也可以是笛卡爾樹,它正好是區間分裂的位置,方便統計區間大小和遞迴左右子樹,可以 \(O(n)\) 解決這個問題。
Code
為了寫這個題口糊了一下怎麼寫笛卡爾樹,沒想到竟然是對的(
#include <bits/stdc++.h> using namespace std; int read(){ char k = getchar(); int x = 0; while(k < '0' || k > '9') k = getchar(); while(k >= '0' && k <= '9') x = x * 10 + k - '0' ,k = getchar(); return x; } const int MX = 5000 + 23; int a[MX] ,ch[MX][2]; stack<int> stk; int size[MX]; int dapai(int x ,int f){ if(x == 0) return 0; int l = dapai(ch[x][0] ,x) ,r = dapai(ch[x][1] ,x); size[x] = 1 + size[ch[x][0]] + size[ch[x][1]]; int Ans = min(l + r + a[x] - a[f] ,size[x]); return Ans; } int main(){ int n = read(); for(int i = 1 ; i <= n ; ++i){ a[i] = read(); if(stk.empty() || a[stk.top()] < a[i]){ stk.push(i); }else{ int las = 0; while(!stk.empty() && a[stk.top()] >= a[i]){ las = stk.top(); stk.pop(); if(!stk.empty() && a[stk.top()] >= a[i]){ ch[stk.top()][1] = las; } } ch[i][0] = las; stk.push(i); } } int root = 0; while(!stk.empty()){ root = stk.top(); stk.pop(); if(!stk.empty()){ ch[stk.top()][1] = root; } } cout << dapai(root ,0) << endl; return 0; }