前言:这个分块和刚被撤下的不同,因为这个分块时间复杂度正确,能通过所有 hack。

题目传送门。

有没有什么可以不用离线都能解决问题的简单算法?答案是分块!!

60pts

首先遇到这个题目,先写一个比较暴力的 \(O(mn)\) 的算法,先排序,降掉一维,剩下一维询问时直接两个二分找到左端点和右端点,然后遍历从左端点到右端点有多少个数满足在第二维的范围内,求和即可。

代码:

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+5;
struct node
{
int x;
int y;
int p;
}a[N];
int cmp(node x,node y)
{
return x.x == y.x?x.y<y.y:x.x<y.x;//排序规则
}
signed main()
{
int n,m;
scanf("%d %d",&n,&m);
for(int i = 1;i<=n;i++)
{
scanf("%d %d %d",&a[i].x,&a[i].y,&a[i].p);
}
sort(a+1,a+n+1,cmp);//排序
for(int i = 1;i<=m;i++)
{
int x1,y1,x2,y2;
scanf("%d %d %d %d",&x1,&y1,&x2,&y2);
int l = 1,r = n,num = 0;
while(l<=r)
{
int mid = l+r>>1;
if(a[mid].x>=x1)//找到左端点
{
num = mid;
r = mid-1;
}
else
{
l = mid+1;
}
}
l = 1,r = n;
int num1 = 0;
while(l<=r)
{
int mid = l+r>>1;
if(a[mid].x<=x2)//找到右端点
{
num1 = mid;
l = mid+1;
}
else
{
r = mid-1;
}
}
long long sum = 0;//这里得开long long
for(int i = num;i<=num1;i++)//遍历
{
int t = a[i].y;
if(t>=y1&&t<=y2)//如果满足
{
sum+=a[i].p;//加上这个基站
}
}
printf("%lld\n",sum);
}
return 0;
}

由于特殊的时间限制,我们获得了 \(60\) 分的好成绩。

100pts

遇到这种题,正解并不好想,考虑优化,复杂度瓶颈在于二分完后的遍历,于是我思来想去都没有想出做法,最终在 arrowpoint 这位大佬的指点下茅塞顿开,AC 了此题,他是怎么想的呢,分块!我们依旧将块长调整为 \(\sqrt{n}\),新建两个块长数组 \(s\) 和 \(s1\),\(s\) 处理的是散块,\(s1\) 处理的是整块,为什么要这样呢?因为我们首先要明白,分块为什么比暴力快?原因很简单,它对于整块有整体处理,这样的话对于每次询问区间 \([l,r]\) 内的各种信息,都可以遍历 \([l,r]\) 内的整块,最坏时间为 \(O(\sqrt{n})\),因为最多只会有 \(\sqrt{n}\) 个块,而每个块都有现成的信息,这样一来每个块都只需要 \(O(1)\) 的时间就能知道这个块的信息,那总时间就是 \(O(1 \times \sqrt{n}) = O(\sqrt{n})\),然后剩下的就是散块,由于散块最多两个,而散块的遍历每个块需要 \(O(\sqrt{n})\) 的时间求信息,所以总时间就是 \(O(2 \times \sqrt{n}) = O(\sqrt{n})\),那么整个程序整体复杂度为 \(O(m\sqrt{n})\)。这里的时间复杂度是忽略常数,所以 \(O(2 \times \sqrt{n}) = O(\sqrt{n})\)。知道了分块速度快的原因,所以说这里为什么要搞两个分块数组?因为散块不需要什么处理,不需要任何辅助加快时间的东西,自然就啥也不用动,但是整块就不一样了,它需要预处理出一些信息,对于这个题,arrowpoint 认为可以将整块处理的基本信息先排个序,因为反正都要算整个块,块里面的数怎么排序都无所谓,然后对于每个整块按照询问中的第二维二分出左右端点,然后求和就行,求和用前缀和优化,这样一来,与处理时间复杂度为 \(O(\sqrt{n} \times (\sqrt{n} \times \log \sqrt{n}))=O(n \log \sqrt{n})\),\(\log \sqrt{n}\) 是个常数,大约为 \(9\),所以说可以默认预处理时间复杂度为 \(O(n)\)。上面说完整块后,散块也就很简单了,直接将两个或一个散块全部遍历一遍,判断是否满足询问条件就行了。

所有的流程大概说了一遍,但这题的细节很多,仅根据上面说的写代码是 A 不了的,现在大概说一下分块的一些公式和注意事项(个人原创):

  • 求第 \(x\) 个块的左端点和右端点,设 \(len\) 为块长,\(n\) 为区间长度,则第 \(x\) 个块的左端点为 \((x-1) \times len+1\),右端点为 \(\min(x \times len,n)\)。这里重点说为什么右端点不是 \(x \times len\),而是 \(\min(x \times len,n)\),因为当 \(n\) 并不是完全平方数时,那这时就会发生 \(len<\sqrt{n}\),\(k>\sqrt{n}\),\(k\) 指的是最大的块编号,这个时候第 \(k\) 个块的长度绝对小于 \(len\),所以使用 \(k \times len\) 是有可能发生越界行为的。
  • 求编号为 \(x\) 的数在块长为 \(len\) 时所在的块编号为 \(\lfloor \frac{x-1}{len} \rfloor+1\)。它其实等价于 \(\lceil \frac{x}{len} \rceil\) 的,如果不懂就在草稿本上分类讨论一下就能明白了。
  • 处理散块时当询问的左端点 \(l\) 和右端点 \(r\) 所在同一个块时,直接从 \(l\) 遍历到 \(r\),否则把 \(l\) 这个块从 \(l\) 到 \(l\) 当前这个块结尾的编号全部遍历一遍,和从 \(r\) 当前这个块的开头的编号到 \(r\) 全部遍历一遍。

说完这些之后,再说本题的注意事项:

  • 十年 OI 一场空,不开 long long 见祖宗!
  • 程序里的二分(对于这道题),可能会有左端点找不到,或者右端点找不到,再或者左端点虽然满足大于等于询问的左端点,但却大于询问的右端点,也或者右端点虽然满足小于等于询问的右端点,但却小于询问的左端点,这些情况都说明对于这个询问,找不到满足询问要求的数,此时这次询问(或求值)答案(或贡献)已经确定为 \(0\),无需继续查下去。

讲这么详细,写代码应该没问题了,这里直接放代码了(当然,会有注释):

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+5;
struct node
{
int x;
int y;
int p;
}a[N];
int cmp(node x,node y)
{
return x.x == y.x?x.y<y.y:x.x<y.x;//照样排序
}
struct node1
{
int y;
int p;
}s[N],s1[N];
long long sum[N];//前缀和得开long long
int id[N];
int cmp1(node1 x,node1 y)//这个是整块的排序
{
return x.y<y.y;
}
signed main()
{
int n,m;
scanf("%d %d",&n,&m);
int len = sqrt(n);//记录块长
for(int i = 1;i<=n;i++)
{
scanf("%d %d %d",&a[i].x,&a[i].y,&a[i].p);
id[i] = (i-1)/len+1;//计算所在块编号
}
sort(a+1,a+n+1,cmp);
for(int i = 1;i<=n;i++)
{
s[i].y = a[i].y;//设置
s[i].p = a[i].p;//设置
s1[i] = s[i];//设置
}
for(int i = 1;i<=id[n];i++)
{
sort(s1+(i-1)*len+1,s1+min(i*len,n)+1,cmp1);//排个序
}
for(int i = 1;i<=n;i++)
{
sum[i] = sum[i-1]+s1[i].p;//求个前缀和
}
for(int i = 1;i<=m;i++)
{
int x1,y1,x2,y2;
scanf("%d %d %d %d",&x1,&y1,&x2,&y2);
int l = 1,r = n,num = 0;
while(l<=r)
{
int mid = l+r>>1;
if(a[mid].x>=x1)//这边寻找第一维的左端点
{
num = mid;
r = mid-1;
}
else
{
l = mid+1;
}
}
if(!num||a[num].x>x2)//如果找不到或者不满足条件
{
printf("0\n");
continue;
}
l = 1,r = n;
int num1 = 0;
while(l<=r)
{
int mid = l+r>>1;
if(a[mid].x<=x2)//找第一维右端点
{
num1 = mid;
l = mid+1;
}
else
{
r = mid-1;
}
}
if(!num1||a[num1].x<x1)//如果找不到或者不满足条件
{
printf("0\n");
continue;
}
long long ss = 0;//得开long long
for(int i = id[num]+1;i<=id[num1]-1;i++)//求整块
{
int l = (i-1)*len+1,r = i*len,num2 = 0;
while(l<=r)
{
int mid = l+r>>1;
if(s1[mid].y>=y1)//在整块中二分找到第二维的左端点
{
num2 = mid;
r = mid-1;
}
else
{
l = mid+1;
}
}
if(!num2||s1[num2].y>y2)//同上
{
continue;
}
l = (i-1)*len+1,r = i*len;
int num3 = 0;
while(l<=r)
{
int mid = l+r>>1;
if(s1[mid].y<=y2)//在整块中二分找到第二维的右端点
{
num3 = mid;
l = mid+1;
}
else
{
r = mid-1;
}
}
if(!num3||s1[num3].y<y1)//同上
{
continue;
}
ss+=sum[num3]-sum[num2-1];//求左端点到右端点的和
}
if(id[num] == id[num1])//如果在同一个块
{
for(int i = num;i<=num1;i++)//直接遍历
{
if(s[i].y>=y1&&s[i].y<=y2)
{
ss+=s[i].p;
}
}
}
else
{
for(int i = num;i<=id[num]*len;i++)//分两次,第一次
{
if(s[i].y>=y1&&s[i].y<=y2)
{
ss+=s[i].p;
}
}
for(int i = (id[num1]-1)*len+1;i<=num1;i++)//第二次
{
if(s[i].y>=y1&&s[i].y<=y2)
{
ss+=s[i].p;
}
}
}
printf("%lld\n",ss);//输出
}
return 0;
}

时间复杂度:\(O(n \log \sqrt{n}+m \sqrt{n} \times \log \sqrt{n})\)。在 3s 内通过绰绰有余。

效率还是不错的,估计卡常一下应该能在一秒内卡过,不过懒得弄了。

如果还有不会的地方,欢迎私信!!

附分块学习网址:这里。

洛谷P3755 [CQOI2017] 老C的任务 题解的更多相关文章

  1. [bzoj4824][洛谷P3757][Cqoi2017]老C的键盘

    Description 老 C 是个程序员. 作为一个优秀的程序员,老 C 拥有一个别具一格的键盘,据说这样可以大幅提升写程序的速度,还能让写出来的程序 在某种神奇力量的驱使之下跑得非常快.小 Q 也 ...

  2. [bzoj4823][洛谷P3756][Cqoi2017]老C的方块

    Description 老 C 是个程序员. 作为一个懒惰的程序员,老 C 经常在电脑上玩方块游戏消磨时间.游戏被限定在一个由小方格排成的R行C列网格上 ,如果两个小方格有公共的边,就称它们是相邻的, ...

  3. 洛谷 P3757 [CQOI2017]老C的键盘

    题面 luogu 题解 其实就是一颗二叉树 我们假设左儿子小于根,右儿子大于根 考虑树形\(dp\) \(f[u][i]\)表示以\(u\)为根的子树,\(u\)为第\(i\)小 那么考虑子树合并 其 ...

  4. 洛谷P3757 [CQOI2017]老C的键盘

    传送门 首先可以直接把整个序列建成一个完全二叉树的结构,这个应该都看得出来 然后考虑树形dp,以大于为例 设$f[i][j]$表示$i$这个节点在子树中排名第$j$位时的总方案数(因为实际只与相对大小 ...

  5. 洛谷$P3756\ [CQOI2017]$老$C$的方块 网络流

    正解:网络流 解题报告: 传送门$QwQ$ 看到不能出现给定的讨厌的图形,简单来说就,特殊边两侧的方格不能同时再连方格. 所以如果出现,就相当于是四种方案?就分别炸四个格子. 然后冷静分析一波之后发现 ...

  6. 洛谷P1484 种树&洛谷P3620 [APIO/CTSC 2007]数据备份 题解(堆+贪心)

    洛谷P1484 种树&洛谷P3620 [APIO/CTSC 2007]数据备份 题解(堆+贪心) 标签:题解 阅读体验:https://zybuluo.com/Junlier/note/132 ...

  7. BZOJ4813或洛谷3698 [CQOI2017]小Q的棋盘

    BZOJ原题链接 洛谷原题链接 贪心或树形\(DP\)都可做,但显然\(DP\)式子不好推(因为我太菜了),所以我选择贪心. 很显然从根出发主干走最长链是最优的,而剩下的点每个都需要走两步,所以用除去 ...

  8. 洛谷 P3700 - [CQOI2017]小Q的表格(找性质+数论)

    洛谷题面传送门 又是一道需要一些观察的数论 hot tea-- 注意到题目中 \(b·f(a,a+b)=(a+b)·f(a,b)\) 这个柿子长得有点像求解 \(\gcd\) 的辗转相除法,因此考虑从 ...

  9. 洛谷P3387 【模板】缩点 题解

    背景 今天\(loj\)挂了,于是就有了闲情雅致来刷\(luogu\) 题面 洛谷P3387 [模板]缩点传送门 题意 给定一个\(n\)个点\(m\)条边有向图,每个点有一个权值,求一条路径,使路径 ...

  10. [NOI导刊2010提高&洛谷P1774]最接近神的人 题解(树状数组求逆序对)

    [NOI导刊2010提高&洛谷P1774]最接近神的人 Description 破解了符文之语,小FF开启了通往地下的道路.当他走到最底层时,发现正前方有一扇巨石门,门上雕刻着一幅古代人进行某 ...

随机推荐

  1. 3.QMainWindow

    QMainWindow介绍 QMainWindow是一个为用户提供主窗口程序的类,包含一个菜单栏(menu bar),多个工具栏(tool bars),多个铆接部件(dock widgets),一个状 ...

  2. 解决Vim粘贴格式乱问题

    在vim粘贴代码的时候,粘贴的代码(shift+insert)会自动缩进,导致格式非常混乱. 本人深受其害,查阅网上大牛,发现以下方式均可行,与大家分享. 下面介绍两种方法: (1)在vim中,进入命 ...

  3. ng-alain 创建页面

    https://ng-alain.com/cli/generate/zh https://ng-alain.com/docs/new-page/zh 默认情况下,创建模块 trade,创建在目录 sr ...

  4. bytecode 生成器

    基础 objectweb asm 很难用,找了几个高级点的. activej codegen 这个库很像 .net DLR 风格,采用 Expression 抽象,例如 Expressions.add ...

  5. DSB的数字正交解调

    1.DSB调制过程 ​ DSB信号是一种双边带调幅调制信号,又叫双边带调幅,通过改变载波的振幅来实现基带数据的传输. 其函数表达式如下: \[s(t) = m(t)*cos(2\pi ft + \va ...

  6. [sa-token]StpUtil.getLoginId

    闲聊 一般情况下,我们想用uid,可能需要前端将uid传过来,或者将token传来,然后我们进行识别. 用了sa-token之后,可以使用StpUtil.getLoginId()方法获取当前会话的用户 ...

  7. Python中指数概率分布函数的绘图详解

    在数据科学和统计学中,指数分布是一种应用广泛的连续概率分布,通常用于建模独立随机事件发生的时间间隔.通过Python,我们可以方便地计算和绘制指数分布的概率密度函数(PDF).本文将详细介绍指数分布的 ...

  8. FISCO BCOS 控制台 部署合约 调用 查看已部署合约的地址

    deploy 部署合约.(默认提供HelloWorld合约和TableTest.sol进行示例使用) 参数: 合约路径:合约文件的路径,支持相对路径.绝对路径和默认路径三种方式.用户输入为文件名时,从 ...

  9. 『玩转Streamlit』--上传下载文件

    在Web应用中,文件的上传下载是交互中不可缺少的功能. 因为在业务功能中,一般不会只有文字的交互,资料或图片的获取和分发是很常见的需求. 比如,文件上传可让用户向服务器提交数据,如上传图片分享生活.提 ...

  10. Solon v3.0.5 发布!(Spring 生态可以退休了吗?)

    Solon 框架! 新一代,面向全场景的 Java 应用开发框架.从零开始构建(非 java-ee 架构),有灵活的接口规范与开放生态. 追求: 更快.更小.更简单 提倡: 克制.高效.开放.生态 有 ...