• P1527 [国家集训队]矩阵乘法(整体二分)


    Link

    整体二分的经典例题。

    对于整体二分,我个人的理解是二分答案套分治。

    具体来说就是对答案进行二分,然后对于询问进行类似于权值线段树求区间第 (k) 大的分治做法。

    首先,我们暴力做法就是对每个询问都跑一边二分答案,这样的复杂度是 O((nm log n))

    这显然我们是不能够接受的。

    我们发现我们二分答案处理每个询问时,会重复计算好多遍加权的操作。

    我们就考虑把询问放在一起处理,每个加权操作只重复计算一次。

    这样就能省去不少时间。具体怎么实现呢?

    假设,我们二分出来的答案为 (mid) 那么此时询问的答案分成了两种情况

    一种是答案小于 (mid) 的,我们可以继续递归下去,

    就像这样:

    紫色的为这个询问的答案。

    另一种是大于 (mid) 的询问,我们就要减去左边的贡献,在 (mid - R) 这个范围递归下去(可以参考权值线段树求解区间第 (k) 大的方法)。

    就像这样

    我们把答案在 (mid) 左边的询问放在前面,答案在 (mid) 右边的询问放在后面。

    这样我们前一部分就可以递归 (L-mid) 这一段区间,后一部分就可以递归 (mid - R) 这个区间。(也就是对询问分治)

    对于这个题,我们怎么判断这个询问的答案是大于或小于 (mid) 的呢?

    我们可以利用二维树状数组,对于 (L-mid) 的这一部分的加权操作加上,然后对于每个询问就相当于二维数点。

    这块区间的点的数量大于 (q[i].c) 我们就说明此时答案小于 (mid),反之大于 (mid).

    一个需要注意的点是,在把询问分成前后两部分的时候,一定要先减去 (L-mid) 的贡献,在赋给中间的那个过渡数组(我在这里卡了近一个小时)

    还有就是要将记录前后两部分询问数量的变量例如 (cntl), (cntr) 要设为局部变量,不然他就会一直加下去导致你答案出错

    具体代码长这样 (带有注释的良心代码)

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<vector>
    using namespace std;
    const int N = 300000;
    int n,m,tot;
    int a[510][510],tr[510][510],ans[N],b[N];
    struct node{ int x,y;};
    struct kkk
    {
    	int x1,x2,id;
    	int y1,y2,c;
    }q[N],tong[N],stal[N],star[N];
    vector< node > sta[N];
    inline int read()
    {
        int s = 0,w = 1; char ch = getchar();
        while(ch < '0' || ch > '9'){if(ch == '-') w = -1; ch = getchar();}
        while(ch >= '0' && ch <= '9'){s = s * 10 + ch - '0'; ch = getchar();}
        return s * w;
    }
    int lowbit(int x){return x & -x;}
    void chenge(int x,int y,int val)//二维树状数组
    {
        for(int i = x; i <= n; i += lowbit(i))
        {
            for(int j = y; j <= n; j += lowbit(j))
            {
                tr[i][j] += val;
            }
        }
    }
    int ask(int x,int y)
    {
        int res = 0;
        for(int i = x; i; i -= lowbit(i))
        {
            for(int j = y; j; j -= lowbit(j))
            {
                res += tr[i][j];
            }
        }
        return res;
    }
    int get_ans(int x1,int y1,int x2,int y2)//二维数点
    {
        return (ask(x2,y2) + ask(x1-1,y1-1) - ask(x1-1,y2) - ask(x2,y1-1));
    }
    void work(int l,int r,int L,int R)//l-r 是我们的答案的区间, L-R 是我们的询问区间
    {
        int cntl = 0, cntr = 0;//这两个变量一定要设为局部变量
        if(l == r)//l==r 说明此时我们 L-R 这一部分询问的答案就是 l
        {
            for(int i = L; i <= R; i++)
            {
                ans[q[i].id] = l;
            }
            return;
        }
        int mid = (l+r)>>1;//二分答案
        for(int i = l; i <= mid; i++)
        {
            for(int j = 0; j < (int)sta[i].size(); j++)
            {
                int x = sta[i][j].x;
                int y = sta[i][j].y;
                chenge(x,y,1);//加权操作
            }
        }
        for(int i = L; i <= R; i++)
        {
            int tmp = get_ans(q[i].x1,q[i].y1,q[i].x2,q[i].y2);//二维数点
            if(tmp >= q[i].c)//如果这个区域的点的数量大于 q[i].c 说明我们这个询问的答案小于 mid 要把他放在前一部分
            {
                stal[++cntl] = q[i];
            }
            else 
    		{
            	q[i].c -= tmp;//后一部分先减去 l-mid 这个答案的贡献,在去 mid - r 这个区间二分答案
            	star[++cntr] = q[i];//把他分到后面的那一部分
            }
        }
        for(int i = l; i <= mid; i++)//把加权操作还原
        {
            for(int j = 0; j < sta[i].size(); j++)
            {
                int x = sta[i][j].x;
                int y = sta[i][j].y;
                chenge(x,y,-1);
            }
        }
        for(int i = L; i <= L + cntl - 1 ; i++) q[i] = stal[i - L + 1];//重新给询问排一下序把答案小于 mid 的放前面,反之放后面
        for(int i = L + cntl; i <= R; i++) q[i] = star[i - L - cntl + 1];
        work(l,mid,L,L + cntl - 1); work(mid + 1,r,L + cntl,R);//继续递归下去
    }
    int main()
    {
        n = read(); m = read();
        for(int i = 1; i <= n; i++)
        {
            for(int j = 1; j <= n; j++)
            {
                a[i][j] = read();
                b[++tot] = a[i][j];
            }
        }
        sort(b + 1,b + tot + 1);//离散化
        int t = unique(b + 1,b + tot + 1) - b - 1;
        for(int i = 1; i <= n; i++)
        {
            for(int j = 1; j <= n; j++)
            {
                a[i][j] = lower_bound(b+1,b+t+1,a[i][j]) - b;
                sta[a[i][j]].push_back((node){i,j});//记录每个权值出现的位置
            }
        }
        for(int i = 1; i <= m; i++)
        {
            q[i].x1 = read(); q[i].y1 = read();
            q[i].x2 = read(); q[i].y2 = read();
            q[i].c = read();  q[i].id = i;
        }
        work(1,t,1,m);//整体二分
        for(int i = 1; i <= m; i++)
        {
            printf("%d
    ",b[ans[i]]);
        }
        return 0;
    }
    
    
  • 相关阅读:
    原来这样就可以开发出一个百万量级的Android相机
    微信读书这样排版,看过的人都很难忘!
    AI小白快上车!这是发往高薪职位的车!
    短视频APP是如何开启你的美好生活的?
    自从我这样撸代码以后,公司网页的浏览量提高了107%!
    如果想成为一名顶尖的前端,这份书单你一定要收藏!
    老板今天问我为什么公司的数据库这么烂,我是这样回答的......
    MapReduce Notes
    HDFS Architecture Notes
    BloomFilter
  • 原文地址:https://www.cnblogs.com/genshy/p/13610162.html
Copyright © 2020-2023  润新知