日常訓練2025-1-24
699. 掉落的方塊
rating:困難
思路(線段樹模擬)
模擬一下這個過程發現,需要維護每個小區間已經有的高度,還要方便的查出小區間的最大值以及整個區間的最大值,所以用線段樹維護。
程式碼
struct Info{
int val = 0;
};
Info operator+(const Info &a, const Info &b){
if (a.val > b.val) return a;
return b;
}
struct SegmentTree {
int n;
std::vector<int> tag;
std::vector<Info> info;
SegmentTree(int n_) : n(n_), tag(4 * n), info(4 * n) {}
// 彙總資訊
void pull(int p) {
info[p] = info[2 * p] + info[2 * p + 1];
}
//懶更新
void add(int p, int v) {
tag[p] += v;
info[p].val += v;
}
// 把資訊發下去
void push(int p) {
add(2 * p, tag[p]);
add(2 * p + 1, tag[p]);
tag[p] = 0;
}
// 查詢是左閉右開的
Info query(int p, int l, int r, int x, int y) {
if (l >= y || r <= x) {
return {};
}
if (l >= x && r <= y) {
return info[p];
}
int m = (l + r) / 2;
push(p);
return query(2 * p, l, m, x, y) + query(2 * p + 1, m, r, x, y);
}
Info query(int x, int y) {
return query(1, 0, n, x, y);
}
void rangeAdd(int p, int l, int r, int x, int y, int v) {
if (l >= y || r <= x) {
return;
}
if (l >= x && r <= y) {
return add(p, v);
}
int m = (l + r) / 2;
push(p);
rangeAdd(2 * p, l, m, x, y, v);
rangeAdd(2 * p + 1, m, r, x, y, v);
pull(p);
}
// 左閉有開
void rangeAdd(int x, int y, int v) {
rangeAdd(1, 0, n, x, y, v);
}
void modify(int p, int l, int r, int x, const Info &v) {
if (r - l == 1) {
info[p] = v;
return;
}
int m = (l + r) / 2;
push(p);
if (x < m) {
modify(2 * p, l, m, x, v);
} else {
modify(2 * p + 1, m, r, x, v);
}
pull(p);
}
void modify(int x, const Info &v) {
modify(1, 0, n, x, v);
}
};
class Solution {
public:
vector<int> fallingSquares(vector<vector<int>>& positions) {
int n = positions.size();
std::vector<int> a;
for (auto e : positions) {
a.push_back(e[0]);
a.push_back(e[1]);
}
std::sort(a.begin(), a.end());
a.erase(std::unique(a.begin(), a.end()), a.end());
int len = a.size();
std::vector<int> ans;
SegmentTree stree(len);
for (int i = 0; i < n; i++){
int pl = positions[i][0], pr = pl + positions[i][1];
int l = std::lower_bound(a.begin(), a.end(), pl) - a.begin();
int r = std::lower_bound(a.begin(), a.end(), pr) - a.begin() - 1;
int oldval = stree.query(l, r + 1).val;
int newval = oldval + positions[i][1];
for (int j = l; j <= r; j++){
stree.modify(j, {newval});
}
ans.push_back(stree.query(0, len).val);
}
return ans;
}
};
E 一起走很長的路!
rating:1700
https://ac.nowcoder.com/acm/contest/95334/E
思路(ST表維護最值)
如果從 1 開始推倒,那麼每個位置之前的重量之和就是字首和,記為 \(f[i-1]\) 。
如果自身重量大於前面的字首和,那我們就需要進行操作。
那麼我們應該怎麼操作呢?
一個比較顯然的貪心想法是,將第 1 塊多米諾骨牌的重量增加 \(a_i - f[i - 1]\)。
那麼對於整個陣列需要給第 1 塊多米諾骨牌增加多少重量呢?顯然是陣列中 \(d_i∈[2,n])\)的最大值。
那麼現在如果是 [l,r]子陣列怎麼辦呢?
在子陣列中應該這樣更新: \(d_i\)。
這時我對子陣列進行一次區間加,再查詢最大值就行了,可以直接使用線段樹暴力處理。
當然,我們再稍微思考一下可以發現,根本不需要進行區間加,直接查詢最大值,然後將最大值加上 fl−1f**l−1 即可,可以使用ST表。
注意,最大值要從 l+1 開始取,因為第 1 塊不需要靠前面的推倒。
時間複雜度 $O(nlogn) $。
程式碼
#include<bits/stdc++.h>
using namespace std;
template <typename T>
class ST{
public:
const int n;
vector<vector<T>> st;
ST(int n = 0, vector<T> &a = {}) : n(n){
st = vector(n + 1, vector<T>(22 + 1));
build(n, a);
}
inline T get(const T &x, const T &y){
return max(x, y);
}
void build(int n, vector<T> &a){
for(int i = 1; i <= n; i++){
st[i][0] = a[i];
}
for(int j = 1, t = 2; t <= n; j++, t <<= 1){
for(int i = 1; i <= n; i++){
if(i + t - 1 > n) break;
st[i][j] = get(st[i][j - 1], st[i + (t >> 1)][j - 1]);
}
}
}
inline T find(int l, int r){
int t = log(r - l + 1) / log(2);
return get(st[l][t], st[r - (1 << t) + 1][t]);
}
};
int main(){
int n, q;
cin >> n >> q;
vector f(n + 1, 0ll), d = f;
for(int i = 1; i <= n; i++){
int x;
cin >> x;
f[i] = f[i - 1] + x;
d[i] = x - f[i - 1];
}
ST<long long> st(n, d);
while(q--){
int l, r;
cin >> l >> r;
if(l == r){
cout << 0 << endl;
continue;
}
auto ma = st.find(l + 1, r);
auto ans = max(ma + f[l - 1], 0ll);
cout << ans << endl;
}
}