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

题意

给你一个长为\(R\)宽为\(C\)的矩阵,第\(i\)\(j\)列的数为\(P_{i,j}\).

\(m\)次询问,每次有5个参数\(x_l,x_r,y_l,y_r,h\). 求以\((x_l,y_l)\)为左上角和\((x_r,y_r)\)为右下角的矩形中,至少要选几个值,使得它们的和\(\geq h\).

数据范围

对于\(50 \%\)的数据,满足\(R,C \le 200,M \le 200,000\)

另有\(50 \%\)的数据,满足\(R=1,C \le 500,000,M \le 20,000\)

对于\(100 \%\)的数据,满足\(1 \le P_{i,j} \le 1,000, 1 \le h \le 2,000,000,000\)

题解

这道题很有意思2333

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

  • 第一个\(R,C \le 200,M \le 200,000\).

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

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

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

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

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

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

    \(n=max(P_{i,j})\)时间复杂度\(O(nRC+m \log n)\),空间复杂度\(O(nRC)\).

  • 第二个\(R=1,C \le 500,000,M \le 20,000\).

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

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

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

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

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

    时间复杂度\(O(C \log n + m \log n)\),空间复杂度\(O(C \log n)\).

代码 ​

/**************************************************************
    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;
}
posted @ 2018-02-26 16:47  zjp_shadow  阅读(...)  评论(...编辑  收藏