BZOJ 1926: [Sdoi2010]粟粟的书架(主席树,二分答案)

题意

给你一个长为R宽为C的矩阵,第ij列的数为Pi,j.

m次询问,每次有5个参数xl,xr,yl,yr,h. 求以(xl,yl)为左上角和(xr,yr)为右下角的矩形中,至少要选几个值,使得它们的和h.

数据范围

对于50%的数据,满足R,C200,M200,000

另有50%的数据,满足R=1,C500,000,M20,000

对于100%的数据,满足1Pi,j1,000,1h2,000,000,000

题解

这道题很有意思2333

这个题相当于二合一吧,两种不同的方法解决它的子问题(但殊途同归).

  • 第一个R,C200,M200,000.

    这个是有点类似于暴力的做法,首先预处理出每种数字出现次数的关于位置和数字大小前缀和,以及出现数字和关于位置数字大小的前缀和.

    这个有点绕,直接举例子吧.比如我程序中的Sum[i][j][k]Tot[i][j][k].

    Sum[i][j][k]就是以(1,1)为左上角(i,j)为右下角矩形的,数字k的和.

    Tot[i][j][k]就是以(1,1)为左上角(i,j)为右下角矩形的,数字k的出现次数的和.

    然后每次就可以二分你需要选的最小的数字了,每次判断可行就直接前缀和容斥就行了.

    然后这个选的最小数字不一定要选满,要最后算一下这个数字要选多少个.

    n=max(Pi,j)时间复杂度O(nRC+mlogn),空间复杂度O(nRC).

  • 第二个R=1,C500,000,M20,000.

    这个就是一个序列操作了,这个我认为是这道题的精髓.

    我们沿用前一个算法的思想,也是要处理序列上那两个东西.

    但时间复杂度肯定要进行优化. 所以有一个数据结构可以支持这个操作,就是主席树!

    主席树不仅支持查找区间第k大,而且还能支持查找区间在[l,r]中数字的和 和 出现数字的和!(看来我数据结构学的真的蠢啊) 以后出现这种题要往这上面想想了.

    然后我们可以每次算答案的时候直接在主席树上二分就行了和刚才的操作差不多吧,也类似于查找第k大.

    时间复杂度O(Clogn+mlogn),空间复杂度O(Clogn).

代码 ​

/************************************************************** Problem: 1926 User: zjp_shadow Language: C++ Result: Accepted Time:4688 ms Memory:509916 kb ****************************************************************/ #include <bits/stdc++.h> #define For(i, l, r) for(register int i = (l), _end_ = (int)(r); i <= _end_; ++i) #define Fordown(i, r, l) for(register int i = (r), _end_ = (int)(l); i >= _end_; --i) #define Set(a, v) memset(a, v, sizeof(a)) using namespace std; bool chkmin(int &a, int b) {return b < a ? a = b, 1 : 0;} bool chkmax(int &a, int b) {return b > a ? a = b, 1 : 0;} inline int read() { int x = 0, fh = 1; char ch = getchar(); for (; !isdigit(ch); ch = getchar() ) if (ch == '-') fh = -1; for (; isdigit(ch); ch = getchar() ) x = (x<<1) + (x<<3) + (ch ^ '0'); return x * fh; } void File() { #ifdef zjp_shadow freopen ("P1926.in", "r", stdin); freopen ("P1926.out", "w", stdout); #endif } const int N = 5e5 + 1e3; const int maxnode = N * 20; int take; inline int Updiv(int a, int b) { return (a / b) + (a % b ? 1 : 0); } struct ChairMan_Tree { int T[N], sumv[maxnode], tot[maxnode], lc[maxnode], rc[maxnode], Size; ChairMan_Tree () { Size = 0; } void Update(int &o, int pre, int l, int r, int up) { o = ++Size; lc[o] = lc[pre]; rc[o] = rc[pre]; sumv[o] = sumv[pre] + up; tot[o] = tot[pre] + 1; if (l == r) return ; int mid = (l + r) >> 1; if (up <= mid) Update(lc[o], lc[pre], l, mid, up); else Update(rc[o], rc[pre], mid + 1, r, up); } void Query(int s, int t, int l, int r, int val) { if (l == r) { take += Updiv(val, l); return ; } int here = tot[rc[t]] - tot[rc[s]], sv = sumv[rc[t]] - sumv[rc[s]], mid = (l + r) >> 1; if (val > sv) { take += here; Query(lc[s], lc[t], l, mid, val - sv); } else Query(rc[s], rc[t], mid + 1, r, val); } } CT; int r, c, m; int Mat[210][210]; int Sum[210][210][1010]; int Tot[210][210][1010]; int sum[N]; void Solve2() { For (i, 1, c) { int val = read(); sum[i] = sum[i - 1] + val; CT.Update(CT.T[i], CT.T[i - 1], 1, 1000, val); } For (i, 1, m) { read(); int l = read(); read(); int r = read(), h = read(); // cout << "Ask: " << l << ' ' << r << ' ' << h << endl; take = 0; if (sum[r] - sum[l - 1] < h) { printf ("Poor QLW\n"); continue ; } CT.Query(CT.T[l - 1], CT.T[r], 1, 1000, h); printf ("%d\n", take); } } inline int Calc_Sum (int xl, int yl, int xr, int yr, int low) { return Sum[xr][yr][low] - Sum[xl - 1][yr][low] - Sum[xr][yl - 1][low] + Sum[xl - 1][yl - 1][low]; } inline int Calc_Tot (int xl, int yl, int xr, int yr, int low) { return Tot[xr][yr][low] - Tot[xl - 1][yr][low] - Tot[xr][yl - 1][low] + Tot[xl - 1][yl - 1][low]; } void Solve1() { For (i, 1, r) For (j, 1, c) { Mat[i][j] = read(); Sum[i][j][Mat[i][j]] += Mat[i][j]; ++Tot[i][j][Mat[i][j]]; For (k, 1, 1000) { Sum[i][j][k] += Sum[i][j - 1][k] + Sum[i - 1][j][k] - Sum[i - 1][j - 1][k]; Tot[i][j][k] += Tot[i][j - 1][k] + Tot[i - 1][j][k] - Tot[i - 1][j - 1][k]; } } For (i, 1, r) For (j, 1, c) Fordown (k, 1000, 1) { Sum[i][j][k] += Sum[i][j][k + 1]; Tot[i][j][k] += Tot[i][j][k + 1]; } For (i, 1, m) { int xi = read(), yi = read(), xj = read(), yj = read(), h = read(); int l = 1, r = 1000, need = -1; while (l <= r) { int mid = (l + r) >> 1; if (Calc_Sum(xi, yi, xj, yj, mid) >= h) need = mid, l = mid + 1; else r = mid - 1; } if (need == -1) { printf ("Poor QLW\n"); continue ; } int Btot = Calc_Tot(xi, yi, xj, yj, need + 1), Bsum = Calc_Sum(xi, yi, xj, yj, need + 1); Btot += Updiv(h - Bsum, need); printf ("%d\n", Btot); } } int main () { File(); r = read(); c = read(); m = read(); if (r == 1) Solve2(); else Solve1(); return 0; }

__EOF__

本文作者zjp_shadow
本文链接https://www.cnblogs.com/zjp-shadow/p/8474037.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   zjp_shadow  阅读(194)  评论(3编辑  收藏  举报
编辑推荐:
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示