前言:这个分块和刚被撤下的不同,因为这个分块时间复杂度正确,能通过所有 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. 如何使用图片压缩降低COS流量成本?

    导语 本文将介绍如何通过[图片压缩]能力,让您降本增效的使用 COS ,文章将写得浅显易懂,旨在快速带领用户了解图片压缩的用法及带来的收益. **** 图片压缩为什么会让您降本增效?******** ...

  2. 云数据备份 | MySQL、SQL Server 数据备份到 COS

    随着互联网高速发展,数据安全的重要性日趋明显.数据备份是企业应对系统故障的重要手段.数据备份可以提高系统的高可用性和灾难可恢复性,使用备份还原数据是系统崩溃时提供数据恢复最小代价的最优方案. 一.云数 ...

  3. 基于Java SpringBoot的音乐网站与分享平台

    @ 目录 摘要 1. 研究背景 2.研究内容 3.系统功能 3.1前台首页功能模块 3.2在线听歌功能模块 3.3后台登录功能模块 3.4在线听歌管理模块 4.部分功能代码实现 5.源码分享(免费获取 ...

  4. 为什么 Llama 3.3 70B 比 GPT-4o 和 Claude 3.5 Sonnet 更优秀

    过去七天的 AI 新闻如狂风暴雨般涌来,AI 世界发生了许多重大变化.在这篇文章中,我们将深入探讨来自 Llama 3.3 70B.GPT-4o 和 Claude 3.5 Sonnet 等主要参与者的 ...

  5. Qt编写视频监控系统79-四种界面导航栏的设计

    一.前言 最初视频监控系统按照二级菜单的设计思路,顶部标题栏一级菜单,左侧对应二级菜单,最初采用图片在上面,文字在下面的按钮方式展示,随着功能的增加,二级菜单越来越多,如果都是这个图文上下排列的按钮, ...

  6. elementPlus 问题总结

    第一次搞,遇上很多弱智问题,记录一下 安装elementPlus $ npm install element-plus --save 全局引入 import ElementPlus from 'ele ...

  7. Many-shot Jailbreaking💘足够长的上下文长度有利于各种越狱?

    这篇文章虽然相较于上一篇图的对应有点迷,但是我感到了作者在强化学习与微调还有数学方面的深厚功底,我甚至感觉他的附录可以再发一篇文章了 这阶段的学习打开了我对越狱的思路~ 禁止盗用,侵权必究!!!欢迎大 ...

  8. Ant和Ivy集成部署和使用

    Apache Ivy是专门用来管理项目的jar包依赖的.我们知道Maven已经有很出色的这方面的功能,如果你已经在使用Maven,就没必要使用Ivy了.但是其实Maven除了这方面功能,还有很多强大的 ...

  9. 前端学习openLayers配合vue3(修改地图样式)

    这一块的东西非常简单,基于上一步的继续操作 关键代码,当然对应的对象需要进行相关的引入,为了方便理解,把背景色和边框放在了一起 //填充颜色 style:new Style({ fill:new Fi ...

  10. 还不会 Cert Manager 自动签发证书?一文掌握

    相信很多小伙伴对于 Cert Manager 不陌生,Cert Manager 是 Kubernetes 上的证书管理工具,基于 ACME 协议与 Let's Encrypt 签发免费证书并为证书自动 ...