日常训练2025-1-24
日常训练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
思路(ST表维护最值)
如果从 1 开始推倒,那么每个位置之前的重量之和就是前缀和,记为
。 如果自身重量大于前面的前缀和,那我们就需要进行操作。
那么我们应该怎么操作呢?
一个比较显然的贪心想法是,将第 1 块多米诺骨牌的重量增加
。 那么对于整个数组需要给第 1 块多米诺骨牌增加多少重量呢?显然是数组中
的最大值。 那么现在如果是 [l,r]子数组怎么办呢?
在子数组中应该这样更新:
。 这时我对子数组进行一次区间加,再查询最大值就行了,可以直接使用线段树暴力处理。
当然,我们再稍微思考一下可以发现,根本不需要进行区间加,直接查询最大值,然后将最大值加上 fl−1f**l−1 即可,可以使用ST表。
注意,最大值要从 l+1 开始取,因为第 1 块不需要靠前面的推倒。
时间复杂度
。
代码
#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; } }
E. Turtle vs. Rabbit Race: Optimal Trainings
rating:1500
思路:二分+小巧思
容易想到的是,存在一个 r 使得跑的节数超过 r 后反而会减小能力值。所以就是在前缀和数组中二分地找这个 r , 此 r 肯定得到的 sum(l, r) 是越靠近 u 越好(靠近可以从左边靠近,也可以从右边靠近),我们二分时找的是小于等于 u 的最大的 r,但是可能还存在一个 > r 的值更优,这个值只可能在 r + 1 位置,所以判断一下即可。
代码
#include <bits/stdc++.h> using namespace std; const int maxn = 2e6 + 5; typedef long long ll; ll a[maxn], pre[maxn]; void solve() { int n; cin >> n; for (int i = 1; i <= n; i++) { cin >> a[i]; pre[i] = pre[i - 1] + a[i]; } int q; cin >> q; while (q--) { int l, target; cin >> l >> target; int r; int left = l, right = n; while (left + 1 < right) { int mid = (left + right) / 2; if (pre[mid] <= target + pre[l - 1]) left = mid; else right = mid; } if (pre[right] <= target + pre[l - 1]) r = right; else r = left; if (r == n || target - (pre[r] - pre[l - 1]) < pre[r + 1] - pre[l - 1] - target ) { cout << r << ' '; } else { cout << r + 1 << ' '; } } cout << endl; } int main() { int T; cin >> T; while (T--) solve(); }
时空的交织
rating:1400
思路(小巧思+DP)
我们考虑乘法分配律:
,也就是说,任取一个子矩形,我们相当于是求a数组的一个子数组之和乘上b数组的一个子数组之和。 如果是一个数组求连续子数组最大和,那么是一个非常经典的问题。但需要注意的是,并非最大乘最大就是本题的答案,还有可能是最小乘最小(两个数组均全负数)、最小乘最大(一个数组全负数,另一个数组全正数)等等。所以需要考虑所有的情况。
代码
#include <bits/stdc++.h> using u64 = unsigned long long; using i64 = long long; typedef std::pair<int, int> pii; const int INF = 0x3f3f3f3f; const int mod = 998244353; const long long LINF = 1e18; void solve(){ int n, m; std::cin >> n >> m; i64 mx1 = -LINF, mn1 = LINF, mx2 = -LINF, mn2 = LINF, mn = 0, mx = 0; std::vector<i64> a(n+1), b(m+1); for (int i = 1; i <= n; i++){ std::cin >> a[i]; a[i] += a[i-1]; mx1 = std::max(mx1, a[i] - mn); mn = std::min(mn, a[i]); mn1 = std::min(mn1, a[i] - mx); mx = std::max(a[i], mx); } mn = 0, mx = 0; for (int i = 1; i <= m; i++){ std::cin >> b[i]; b[i] += b[i-1]; mx2 = std::max(mx2, b[i] - mn); mn = std::min(mn, b[i]); mn2 = std::min(mn2, b[i] - mx); mx = std::max(b[i], mx); } i64 ans = -LINF; for (auto x : {mx1, mn1}){ for (auto y : {mx2, mn2}){ ans = std::max(1LL * x * y, ans); } } std::cout << ans << '\n'; } signed main(){ std::ios::sync_with_stdio(false); std::cin.tie(nullptr); std::cout<<std::setiosflags(std::ios::fixed)<<std::setprecision(15); int t = 1, i; for (i = 0; i < t; i++){ solve(); } return 0; }
本文作者:califeee
本文链接:https://www.cnblogs.com/califeee/p/18689635
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步