日常训练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

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;
    }
}

E. Turtle vs. Rabbit Race: Optimal Trainings

rating:1500

https://codeforces.com/problemset/problem/1933/E

思路:二分+小巧思

容易想到的是,存在一个 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

https://ac.nowcoder.com/acm/contest/67746/I

思路(小巧思+DP)

我们考虑乘法分配律:\((a_i+a_j)*(b_x+b_y)=a_i * b_x+a_i * b_y + a_j * b_x + a_j * b_y\)​,也就是说,任取一个子矩形,我们相当于是求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;
}
posted @ 2025-01-24 15:50  califeee  阅读(4)  评论(0编辑  收藏  举报