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\n",b[ans[i]]);
}
return 0;
}

P1527 [国家集训队]矩阵乘法(整体二分)的更多相关文章

  1. 洛谷P1527 [国家集训队] 矩阵乘法 [整体二分,二维树状数组]

    题目传送门 矩阵乘法 题目描述 给你一个N*N的矩阵,不用算矩阵乘法,但是每次询问一个子矩形的第K小数. 输入输出格式 输入格式: 第一行两个数N,Q,表示矩阵大小和询问组数: 接下来N行N列一共N* ...

  2. 洛谷$P1527$ [国家集训队]矩阵乘法 整体二分

    正解:整体二分 解题报告: 传送门$QwQ$ 阿看到这种查询若干次第$k$小显然就想到整体二分$QwQ$? 然后现在就只要考虑怎么快速求出一个矩形内所有小于某个数的数的个数? 开始我的想法是离散化然后 ...

  3. P1527 [国家集训队]矩阵乘法 [整体二分]

    权值排序,整体二分,没了. // by Isaunoya #include <bits/stdc++.h> using namespace std; #define rep(i, x, y ...

  4. Luogu1527 [国家集训队]矩阵乘法 (整体二分)(Unfinished)

    全线RE的代码... 先搁这吧,下次再说.flag //#include <iostream> #include <cstdio> //#include <cstring ...

  5. 洛谷 P1527 [国家集训队]矩阵乘法 解题报告

    P1527 [国家集训队]矩阵乘法 题目描述 给你一个\(N*N\)的矩阵,不用算矩阵乘法,但是每次询问一个子矩形的第\(K\)小数. 输入输出格式 输入格式: 第一行两个数\(N,Q\),表示矩阵大 ...

  6. [洛谷P1527] [国家集训队]矩阵乘法

    洛谷题目链接:[国家集训队]矩阵乘法 题目背景 原 <补丁VS错误>请前往P2761 题目描述 给你一个N*N的矩阵,不用算矩阵乘法,但是每次询问一个子矩形的第K小数. 输入输出格式 输入 ...

  7. P1527 [国家集训队]矩阵乘法

    \(\color{#0066ff}{ 题目描述 }\) 给你一个N*N的矩阵,不用算矩阵乘法,但是每次询问一个子矩形的第K小数. \(\color{#0066ff}{输入格式}\) 第一行两个数N,Q ...

  8. 【LG1527】[国家集训队]矩阵乘法

    [LG1527][国家集训队]矩阵乘法 题面 洛谷 题解 我也不知道为什么取个这样的名字... 其实就是区间\(kth\)扩展到二维 还是用整体二分搞啦,把树状数组换成二维的 其他的基本没有什么差别 ...

  9. Luogu-1527 [国家集训队]矩阵乘法

    Luogu-1527 [国家集训队]矩阵乘法 题面 Luogu-1527 题解 昨天学CDQ分治时做了一些题,但是因为题(wo)太(tai)水(lan)了(le)并没有整理 学了一晚上的整体二分,拿这 ...

随机推荐

  1. myblogplus 第三期 如何更改你博客的图标,已实现 - mooling原创

    三言两语 博客的logo可以凸显你的blog的个性 不知道你有没有觉得博客园原始的那个小矿工不好看了呢 fromto 这才是个人博客的style! 为什么要写这篇文章 因为在博客园的“找找看”中,如果 ...

  2. HDU—2021-发工资咯(水题,有点贪心的思想)

    作为杭电的老师,最盼望的日子就是每月的8号了,因为这一天是发工资的日子,养家糊口就靠它了,呵呵  但是对于学校财务处的工作人员来说,这一天则是很忙碌的一天,财务处的小胡老师最近就在考虑一个问题:如果每 ...

  3. Azure Blob (三)参数设置说明

    一,引言 上一篇将 Azure Blob 存储的时候,有使用到一个 .NET  Core Web 项目,通过代码的方式进行操作 Azure Blob 的数据,接着上一篇的内容,今天继续看一下代码,具体 ...

  4. 09 promise then

    then() 方法返回一个 Promise 链式调用:then里面回调函数(成功回调和失败回调),凡事这两个回调函数里面抛出错误或者返回一个已经是拒绝状态的 Promise. 那么 then 返回的  ...

  5. Java中构建长字符串的四种模式

    回字有四种写法,构建字符串也有四种方式. 用+号最快,第二第三种可读性好,第四种是log4j自有的.下面请见代码: package logbackCfg; import java.text.Messa ...

  6. 20190923-06Linux文件权限类 000 014

    文件属性 Linux系统是一种典型的多用户系统,不同的用户处于不同的地位,拥有不同的权限.为了保护系统的安全性,Linux系统对不同的用户访问同一文件(包括目录文件)的权限做了不同的规定.在Linux ...

  7. python应用 曲线拟合 02

    前情提要 CsI 闪烁体晶体+PD+前放输出信号满足: $U(t) = \frac{N_f\tau_p}{\tau_p-\tau_f} \left[ e^{-\frac{t}{\tau_p}}-e^{ ...

  8. JZOJ1496 页

    Description 战神阿瑞斯听说2008年在中华大地上,将举行一届规模盛大的奥林匹克运动会,心中顿觉异常兴奋,他想让天马在广阔的天空上,举行一场精彩的天马队列变换表演.首先,战神安排n头高度不同 ...

  9. [LeetCode]603. 连续空余座位(Mysql、自连接)

    题目 几个朋友来到电影院的售票处,准备预约连续空余座位. 你能利用表 cinema ,帮他们写一个查询语句,获取所有空余座位,并将它们按照 seat_id 排序后返回吗? | seat_id | fr ...

  10. [LeetCode]面试题 01.06. 字符串压缩

    题目 字符串压缩.利用字符重复出现的次数,编写一种方法,实现基本的字符串压缩功能.比如,字符串aabcccccaaa会变为a2b1c5a3.若"压缩"后的字符串没有变短,则返回原先 ...