粟粟的书架(主席树+前缀和)

题目链接粟粟的书架
题目大意: 给你一个 n × m n \times m n×m的矩阵, q q q个询问, 每次给你一个左上角在 ( x 1 , y 1 ) (x1,y1) (x1,y1),右下角在 ( x 2 , y 2 ) (x2,y2) (x2,y2)的子矩阵, 和一个权值 h h h, 要求在子矩阵中选出最少的数,使得这些数的和不小于 h h h.
思路:
注意到题目数据有 50 % 50 \% 50% { n ≤ 200 , n ≤ 200 , q ≤ 2 × 1 0 5 } \{n \leq 200, n \leq 200, q \leq 2 \times 10^5\} {n200,n200,q2×105}, 这样我们可以对这 50 % 50\% 50%的数据进行前缀和暴力预处理, 设 c n t _ k [ i ] [ j ] [ k ] cnt\_k[i][j][k] cnt_k[i][j][k] ( 1 , 1 ) 到 ( i , j ) (1,1) 到 (i,j) (1,1)(i,j)这个子矩阵中 ≥ k \geq k k的个数, s u m _ k [ i ] [ j ] [ k ] 为 ( 1 , 1 ) 到 ( i , j ) sum\_k[i][j][k]为(1,1) 到(i,j) sum_k[i][j][k](1,1)(i,j)这个子矩阵中 ≥ k \geq k k的元素和.这样我们就可以二分 k k k,来进行求解.
另外 50 % 50\% 50%的数据是 { n = 1 , m ≤ 5 × 1 0 5 , q ≤ 2 × 1 0 4 } \{n=1, m \leq 5 \times 10^5 , q \leq 2 \times 10^4\} {n=1,m5×105,q2×104}, 那么就变成了区间 ( y 1 , y 2 ) (y1,y2) (y1,y2)内找出最少的数使其和不小于 h h h, 我们可以用主席树来求解, 并二分 k k k使区间内大于等于 k k k的元素和不小于 h h h.
注意, 这里二分过程中求解的次数并非最小次数, 还需要将多余的 ⌈ h k ⌉ \lceil \frac{h}{k} \rceil kh向上取整转化为次数

#include<bits/stdc++.h>
using namespace::std;
#define fi first
#define se second
#define lc (x << 1)
#define rc (x << 1 | 1)
#define mid ((l + r) >> 1)
#define zty(x) cout << #x << " = " << x << '\n';
const int MAX_N = 5e5 + 5;
const int MAX_M = 2e6 + 5;
const int mod = 998244353;
const int INF = 0x3f3f3f3f;

int n, m, q;
struct PresidentTree {
    int rt[MAX_N], tot;
    int sum[MAX_N * 32], cnt[MAX_N * 32], ls[MAX_N * 32], rs[MAX_N * 32];
    int update(int p, int l, int r, int val) {
        int x = ++ tot;
        sum[x] = sum[p] + val;
        cnt[x] = cnt[p] + 1;
        if(l == r) return x;
        if(val <= mid) ls[x] = update(ls[p], l, mid, val), rs[x] = rs[p];
        else rs[x] = update(rs[p], mid + 1, r, val), ls[x] = ls[p];
        return x;
    }
    int query(int p, int q, int l, int r, int limit) {
        if(l == r) return (limit + l - 1) / l;
        int rcnt = cnt[rs[p]] - cnt[rs[q]];
        int rsum = sum[rs[p]] - sum[rs[q]];
        if(rsum >= limit) return query(rs[p], rs[q], mid + 1, r, limit);
        else return query(ls[p], ls[q], l, mid, limit - rsum) + rcnt;
    }
} PT;
int a[205][205];
int sum_k[205][205][1005];
int cnt_k[205][205][1005];
void solve() {
    cin >> n >> m >> q;
    if(n == 1) {
        for(int i = 1; i <= m; ++ i) {
            int x;
            cin >> x;
            PT.rt[i] = PT.update(PT.rt[i - 1], 1, 1000, x);
        }
        while(q -- ) {
            int x1, y1, x2, y2, h;
            cin >> x1 >> y1 >> x2 >> y2 >> h;
            if(PT.sum[PT.rt[y2]] - PT.sum[PT.rt[y1 - 1]] < h) {
                cout << "Poor QLW" << '\n';
            } else {
                cout << PT.query(PT.rt[y2], PT.rt[y1 - 1], 1, 1000, h) << '\n';
            }
        }
    } else {
        for(int i = 1; i <= n; ++ i) {
            for(int j = 1; j <= m; ++ j) cin >> a[i][j];
        }
        for(int k = 1; k <= 1000; ++ k) {
            for(int i = 1; i <= n; ++ i) {
                for(int j = 1; j <= m; ++ j) {
                    sum_k[i][j][k] = sum_k[i - 1][j][k] + sum_k[i][j - 1][k] - sum_k[i - 1][j - 1][k] + (a[i][j] >= k ? a[i][j] : 0);
                    cnt_k[i][j][k] = cnt_k[i - 1][j][k] + cnt_k[i][j - 1][k] - cnt_k[i - 1][j - 1][k] + (a[i][j] >= k ? 1 : 0);
                }
            }
        }
        auto Get_sum = [&](int x1, int y1, int x2, int y2, int k) {
            return sum_k[x2][y2][k] - sum_k[x1 - 1][y2][k] - sum_k[x2][y1 - 1][k] + sum_k[x1 - 1][y1 - 1][k];
        };
        auto Get_cnt = [&](int x1, int y1, int x2, int y2, int k) {
            return cnt_k[x2][y2][k] - cnt_k[x1 - 1][y2][k] - cnt_k[x2][y1 - 1][k] + cnt_k[x1 - 1][y1 - 1][k];
        };
        while(q -- ) {
            int x1, y1, x2, y2, h;
            cin >> x1 >> y1 >> x2 >> y2 >> h;
            if(Get_sum(x1, y1, x2, y2, 1) < h) {
                cout << "Poor QLW" << '\n';
            } else {
                int l = 1, r = 1000, ret = 0;
                while(l <= r) {
                    if(Get_sum(x1, y1, x2, y2, mid) >= h) {
                        ret = mid;
                        l = mid + 1;
                    } else {
                        r = mid - 1;
                    }
                }
                cout << Get_cnt(x1, y1, x2, y2, ret) - (Get_sum(x1, y1, x2, y2, ret) - h) / ret << '\n';
            }
        }
    }
}
signed main() {
//    freopen("SPO.IN", "r", stdin);
//    freopen("SPO.OUT", "w", stdout);
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
    int ZTY = 1;
    //cin >> ZTY;
    while(ZTY -- ) solve();
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值