洛谷P1496 火烧赤壁【题解】
事先声明
本题解文字比较多,较为详细,算法为离散化和差分,如会的大佬可以移步去别处看这道题的思路(因为作者比较懒,不想新开两个专题)。
题目简要
给定每个起火部分的起点和终点,请你求出燃烧位置的长度之和。
注意:左闭右开
浅浅来谈一下
看到这道题时,你肯定有很多疑问。
为什么作者要搞左闭右开啊?
有重复的区间怎么搞?
我c, \(a\) 和 \(b\) 的范围在 \(-2^{31} \le a,b \le 2^{31}\) 之间?
光是这几个问题就可以把你难倒了吗?
不可能!我们先由简到繁,转换问题。
转换问题
有一段区间,范围为 \(0 \le 10^6\) ,其他的和原题一样。
我们在把问题抽象化。
问题描述
一段区间,每个点有 \(0,1\) 两个状态,初始每个点都是 \(0\) 。
每次操作选择一对 \(l,r\) ,将区间 \(l,r\) 变成 \(1\) ,问最后这一段区间 \(1\) 的个数?
因为题目的范围不支持我们进行 \(O(MAX\ a)\) 的遍历,所以,我们要想办法进行时间复杂度的优化,让他变成 \(O(1)\) 或 \(O(\log_a)\) 。
这样涉嫌区间问题,时间复杂度压缩的问题,可以考虑差分。
差分
绕过本题,我们看另外一题
问题描述:P2367 语文成绩
给定一对 \(n,p\) ,表示有 \(n\) 名同学,需要进行 \(p\) 次操作。
每次操作给定 \(x,y,z\) 表示从第 \(x\) 名同学到第 \(y\) 同学加上 \(z\) 的分数。
问全班最低分?
满足 \(1 \le n,p \le 5*10^6\)
浅浅谈一下
乍一看,第一反应就是暴力。
暴力遍历 \(x,y\) ,时间复杂度为 \(O(pn)\) ,明显支持不了。
这里考虑一种新的方法:差分。
我们设 \(cf_i=a_i-a_{i-1}\) ,特殊的,我们令 \(cf_1=a_1\) 。
明显的,我们会发现:
\sum_{i=1}^ncf_i&=a_1 + (a_2-a_1) + (a_3-a_2) + ....(a_n-a_{n-1})\\
&=a_n
\end{aligned}
\]
于是,我们发现了一个很重要的结论,差分数组的前缀和等于原数组。
我们在来看怎么修改。
我们把求原数组当成把差分数组从前往后扫一遍。
假设我们要修改 \([x,y]\) 的值,我们从前往后扫,扫到 \(x\) 前都没有问题,扫到了 \(x\) 了!诶,要加 \(w\) ,我们再次往后扫, \(y\) 前面也畅通无阻,但是扫到 \(y+1\) 的时候多了 \(w\) ,我们把他减掉,再往后扫,发现都没问题了,因为一加一减抵消了!
所以,我们可以归纳得:修改 \([x,y]\) 时, \(cf_x++\) , \(cf_{y+1}--\) 。
所以,这道题我们就做出来了。
Code
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 5e6 + 7;
int n, p, a[MAXN], w[MAXN];
int main() {
cin >> n >> p;
for (int i = 1; i <= n; i ++) cin >> a[i], w[i] = a[i] - a[i - 1];
for (int i = 1; i <= p; i ++) {
int x, y, z;
cin >> x >> y >> z;
w[x] += z, w[y + 1] -= z;
}
int ans = 1e9, sum = 0;
for (int i = 1; i <= n; i++) sum += w[i], ans = min(ans, sum);
cout << ans << endl;
return 0;
}
回到抽象化的问题
我们重新看这题,是不是很简单了?这不就是差分的模板嘛。
我们按照刚才的处理方法处理一遍,求下前缀和,看看哪些是 \(1\) 统计就行。
回到原题
我们可以发现,我们刚才一个很重要的元素就是:差分数组。
但是原题是: \(-2^{31} \le a,b \le 2^{31}\) ,怎么搞数组?
这里就要用到另外一种方法:离散化。
又离开本题(QwQ)
问题描述:
给定一串数,求出每个数在数列中的排名。
满足 \(-10^9 \le a_i \le 10^9\) 。
又浅浅谈一下(TwT)?
我们发现问题求的是排名,很容易发现,排名符合两个性质。
- 把很大的区间映射到很小的区间。
- 原数组每两个元素的大小关系不变。
只要遇到的问题符合这种性质,我们就考虑用离散化。
离散化三部曲
- 排序
一定要排序!!因为后面的函数需要排序,时间为 \(O(n\log_n)\) ,这里不考虑值域(你看看值域多大?) - 去重
使用 \(unique\) 函数,使用格式: \(unique(a.begin(),a.end())\) 这里 \(a.begin(),a.end()\) 分别指数组的头指针和尾指针,普通数组用 \(a+1,a+1+n\)
注意一点:该函数的返回值是不属于去重数组的第一个地址,比如有个数组长度为 \(n\) ,去重数组长度为 \(k\) ,他的返回值就是 \(a_{k+1}的地址\)。
根据上面几点,我们可以推出公式。
\]
这样我们就可以求出去重数组的长度了。
3. 求名次了
我们这里了解一个函数: \(lower_\ bound(a+1,a+1+n,x)\) 他返回的是第一个大于等于 \(x\) 的元素的地址,注意:必须有序。
于是我们就可以推出一个公式:( \(rk\) 数组为名词数组)。
\]
\(b\) 数组为去重数组, \(a\) 数组为原数组(应该可以理解吧……)。
至此,第二个分任务已经完成。
Code:
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1e5 + 7;
int T, a[MAXN], b[MAXN], rk[MAXN];
int main() {
for (cin >> T; T; T--) {
int n;
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i];
memcpy(b + 1, a + 1, n * sizeof(int));
sort(b + 1, b + 1 + n);
int *ed = unique(b + 1, b + 1 + n);
for (int i = 1; i <= n; i++) {
rk[i] = lower_bound(b + 1, ed, a[i]) - (b + 1) + 1;
cout << rk[i] << ' ';
}
cout << endl;
}
return 0;
}
再一次回到原题
我们有离散化,差分,天下不就容易打了吗?(bushi)
最后一次浅浅谈一下
我们考虑把读进来的 \(2n\) 个数全部进行离散化(先别问为什么)。
这样就会形成 \(2*n-1\) 个区间,我们把每一个区间当成一个元素进行差分。
比如 \(1\) 和 \(2\) 中间是 \(1\) 区间,所以要更新 \([l,r]\) 时,就是更新 \([l,r-1]\) 个区间,具体的:
\]
最后,求出前缀和,若第 \(i\) 区间之间是 \(\ge 1\) 的,就把 \(b[i+1] - b[i]\) 累加到 \(ans\) 里就行。
所以,这道题我们就做完了。
但是真的做完了吗?
question1
- 边界条件考虑了吗?
既然是左闭右开,右边的就不算。
刚刚我在讲的时候快速忽略了一个点,为什么区间的长度为 \(b[i+1]-b[i]\) ,不是 \(b[i+1]-b[i]+1\) 吗?
我们来举一个例子:
区间 \([2,7)\) 分为 \([2,4),[4 7)\) 他们的和是不是相等的。
为什么举这个例子呢?如果你认真去做这道题目,发现离散化把他们拆成许多个小的区间,要把他们加起来凑成一个大区间,所以我才举了这个例子。
可以发现 \([2,7)\) 为 \(2,3,4,5,6\) ,而 \([2,4),[4 7)\) 为 \(2,3,\ 4,5,6\) 发现是完全一样的,所以作者左闭右开是帮助我们更好的写代码(谢谢~~)
question2
这是一个扩展,有什么公式可以用 \(b\) 和 \(rk\) 表示 \(a\) 吗?
这里是有的,这里给出公式。
\]
至于证明,请读者(包括以后的我)自己思考,如果想不会请在评论区打出来,我会解答。
Code
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e4 + 7;
int n, a[2 * MAXN], b[2 * MAXN], rk[2 * MAXN], cf[2 * MAXN], cnt;
int main() {
ios::sync_with_stdio(false);
cin.tie(NULL);
cin >> n;
for (int i = 1; i <= n; i ++)
cin >> a[++ cnt], cin >> a[++ cnt];
memcpy(b + 1, a + 1, 2 * n * sizeof(int));
sort(b + 1, b + 1 + 2 * n);
int k = unique(b + 1, b + 1 + 2 * n) - (b + 1);
for (int i = 1; i <= 2 * n; i++)
rk[i] = lower_bound(b + 1, b + 1 + k, a[i]) - b;
for (int i = 1; i <= 2 * n; i += 2) {
int r =rk[i + 1], l = rk[i];
cf[l] ++, cf[r] --;
}
int ans = 0, sum = 0;
for (int i = 1; i <= k; i++) {
sum += cf[i];
if (sum > 0) ans += b[i + 1] - b[i];
}
cout << ans;
return 扶苏咕咕咕~~~;
}
完结撒花✿✿ヽ(°▽°)ノ✿
洛谷P1496 火烧赤壁【题解】的更多相关文章
- 洛谷 P1496 火烧赤壁
题目描述 曹操平定北方以后,公元208年,率领大军南下,进攻刘表.他的人马还没有到荆州,刘表已经病死.他的儿子刘琮听到曹军声势浩大,吓破了胆,先派人求降了. 孙权任命周瑜为都督,拨给他三万水军,叫他同 ...
- 洛谷P1496 火烧赤壁 (模拟/离散化+差分)
分析可知:将起点和终点按照从小到大的顺序排序,对答案不会产生影响 所以此时我们得到一种模拟做法: 1 #include<bits/stdc++.h> 2 using namespace s ...
- 洛谷NOIp热身赛题解
洛谷NOIp热身赛题解 A 最大差值 简单树状数组,维护区间和.区间平方和,方差按照给的公式算就行了 #include<bits/stdc++.h> #define il inline # ...
- 洛谷P2827 蚯蚓 题解
洛谷P2827 蚯蚓 题解 题目描述 本题中,我们将用符号 ⌊c⌋ 表示对 c 向下取整. 蛐蛐国最近蚯蚓成灾了!隔壁跳蚤国的跳蚤也拿蚯蚓们没办法,蛐蛐国王只好去请神刀手来帮他们消灭蚯蚓. 蛐蛐国里现 ...
- 洛谷P1816 忠诚 题解
洛谷P1816 忠诚 题解 题目描述 老管家是一个聪明能干的人.他为财主工作了整整10年,财主为了让自已账目更加清楚.要求管家每天记k次账,由于管家聪明能干,因而管家总是让财主十分满意.但是由于一些人 ...
- [POI 2008&洛谷P3467]PLA-Postering 题解(单调栈)
[POI 2008&洛谷P3467]PLA-Postering Description Byteburg市东边的建筑都是以旧结构形式建造的:建筑互相紧挨着,之间没有空间.它们共同形成了一条长长 ...
- [NOI 2020 Online] 入门组T1 文具采购(洛谷 P6188)题解
原题传送门 题目部分:(来自于考试题面,经整理) [题目描述] 小明的班上共有 n 元班费,同学们准备使用班费集体购买 3 种物品: 1.圆规,每个 7 元. 2.笔,每支 4 元. 3.笔记本,每本 ...
- [洛谷P3948]数据结构 题解(差分)
[洛谷P3948]数据结构 Description 最开始的数组每个元素都是0 给出n,opt ,min,max,mod 在int范围内 A: L ,R ,X 表示把[l,R] 这个区间加上X(数组的 ...
- [CodePlus 2017 11月赛&洛谷P4058]木材 题解(二分答案)
[CodePlus 2017 11月赛&洛谷P4058]木材 Description 有 n棵树,初始时每棵树的高度为 Hi ,第 i棵树每月都会长高 Ai.现在有个木料长度总量为 S的订单, ...
- 洛谷P1189 SEARCH 题解 迭代加深
题目链接:https://www.luogu.com.cn/problem/P1189 题目大意: 给你一个 \(n \times m\) 的矩阵,其中有一些格子可以走,一些各自不能走,然后有一个点是 ...
随机推荐
- python模拟登录获取网站cookie
因工作的需要需要使用某第三方网站页面的功能,但其网站未提供API,只有登录用户可使用该功能. 之前试过php使用snoopy获取set-cookie内容项进行手动拼装cookie,使用一段时间后发现网 ...
- 用于数据科学的顶级 C/C++ 机器学习库整理
用于数据科学的顶级 C/C++ 机器学习库整理 介绍和动机--为什么选择 C++ C++ 非常适合 动态负载平衡. 自适应缓存以及开发大型大数据框架 和库.Google 的MapReduce.Mong ...
- Java读取txt文件、excel文件的方法
Java读取txt文件.excel文件的方法 1.读取txt文件 public static String getFileContent(String filePath,String charset) ...
- 【安装文档】TRex流量分析仪保姆级安装指南--基于VMware虚拟机(ubantu18.04@Intel 82545EM)
前言 既然你已经知道TRex并尝试搜索它的安装教程,这意味着你有一定的基础知识(至少知道自己需要什么).因此本文对于TRex的介绍部分会偏少 本次主要为TRex安装过程的一次记录(版本为v3.0.0) ...
- 修改egg.js项目的默认favicon图标
在项目根目录下的config/config.default.js文件中配置 将 .ico的图标放在一个目录中 方法一.读取本地文件 const path = require('path'); cons ...
- phpexcel 小技巧
//设置填充的样式和背景色$objPHPExcel->getActiveSheet()->getStyle( 'A1:AU1')->getFill()->setFillType ...
- vue 项目中,后端返回文件流,导出excel
之前写过文件流导出excel,这次直接把上次的代码拿过来复制粘贴,但是导出的表格里面没有数据,只显示undefined. 这是之前的代码 // api接口页面 // excel导出接口 export ...
- i春秋Fuzzing
先查看源码...没东西,抓包 发现也没什么,但是右边有个提示hint: ip,Large internal network(最大内网ip) 可能需要我们伪造代码进行访问,这还不简单,直接在reques ...
- elasticsearch bucket 之rare terms聚合
目录 1.背景 2.需求 3.前置准备 3.1 准备mapping 3.2 准备数据 4.实现需求 4.1 dsl 4.2 java代码 4.3 运行结果 5.max_doc_count 和 sear ...
- 使用VMware安装Linux(CentOS)操作系统
使用VMware安装CentOS 6.4 环境:Windows7 , VMware Workstation10, CentOS6.4 为什么选择CentOS ? 主流: 目前的Linux操作系统主要应 ...