P1496 火烧赤壁 【思维】

在一场历史性的战役中,你需要统计连环船上火势蔓延的总长度。面对曹操的怒火,运用算法技巧,计算出各段火线覆盖的距离之和。这不仅是对历史的回顾,也是对数据处理能力的一次考验。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目背景

曹操平定北方以后,公元 208 年,率领大军南下,进攻刘表。他的人马还没有到荆州,刘表已经病死。他的儿子刘琮听到曹军声势浩大,吓破了胆,先派人求降了。

孙权任命周瑜为都督,拨给他三万水军,叫他同刘备协力抵抗曹操。

隆冬的十一月,天气突然回暖,刮起了东南风。

没想到东吴船队离开北岸大约二里距离,前面十条大船突然同时起火。火借风势,风助火威。十条火船,好比十条火龙一样,闯进曹军水寨。那里的船舰,都挤在一起,又躲不开,很快地都烧起来。一眨眼工夫,已经烧成一片火海。

曹操气急败坏的把你找来,要你钻入火海把连环线上着火的船只的长度统计出来!
题目描述

给定每个起火部分的起点和终点,请你求出燃烧位置的长度之和。
输入格式

第一行一个整数,表示起火的信息条数 nnn。
接下来 nnn 行,每行两个整数 a,ba, ba,b,表示一个着火位置的起点和终点。
输出格式

输出一行一个整数表示答案。
输入输出样例
输入 #1

3
-1 1
5 11
2 9

输出 #1

11

说明/提示
数据规模与约定

对于全部的测试点,保证 1≤n≤2×1041 \leq n \leq 2 \times 1041≤n≤2×104,−231≤a≤b<231-2{31} \leq a \leq b \lt 2^{31}−231≤a≤b<231,且答案小于 2312^{31}231。

#include <iostream>
#include <algorithm>
#include <cstdio>
using namespace std;

typedef long long ll;
const int maxn = 2e5 + 10;
int a[maxn];
int b[maxn];
int ls[maxn];
bool vis[maxn];
int n, tot;

int main() {
	scanf("%d", &n);
	for(int i= 1; i <= n; i++) {
		scanf("%d%d", &a[i], &b[i]);
	}
	sort(a + 1, a + 1 + n);
	sort(b + 1, b + 1 + n);
	ll ans = 0;
	for(int i = 1; i <= n; i++) {
		ans += b[i] - a[i];
		if(i < n && b[i] > a[i + 1])	
			ans -= b[i] - a[i + 1]; 
	} 
	printf("%lld", ans);
	return 0;
}
<think>好的,我现在需要解决用户关于洛谷P1496火烧赤壁问题的离散化处理方法,尤其是代码实现。首先,我得回忆一下离散化的基本概念和应用场景。离散化主要用于处理大范围数据但数据点本身稀疏的情况,比如这里的区间覆盖问题,虽然坐标范围很大,但实际的区间端点数量相对较少。通过离散化,可以将这些大范围的坐标映射到更小的连续整数上,从而方便处理。 用户提到的题目要求计算多个区间覆盖后的总长度,而直接处理大范围的坐标会导致内存和时间上的问题。所以离散化结合差分的方法应该适用。根据引用[2]和[5],使用差分数组来标记区间的开始和结束,然后通过前缀和来确定覆盖区域。不过,如何将原始的大坐标映射到离散化的索引是关键。 接下来需要确定离散化的步骤。根据引用[3]和[4],首先需要收集所有的区间端点,然后排序并去重。这里需要注意的是,是否需要去重取决于离散化后的处理方式。例如,在差分数组中,每个离散化后的点代表一个单位区间,所以相邻点之间的原始差距会被保留。但如果在离散化时不去重,可能会导致重复处理相同的点,增加计算量。所以通常需要去重,确保每个坐标点唯一。 然后,如何将原始坐标映射到离散化后的索引。这通常通过二分查找来实现,找到每个原始坐标在排序后的数组中的位置。例如,使用Python中的bisect模块来进行查找。 接下来考虑差分数组的处理。每个原始区间[l, r]会被离散化为离散化数组中的两个点,假设离散化后的索引为i和j,那么差分数组在i处加1,在j处减1。这里的处理需要注意区间是左闭右开还是闭区间。根据引用[2],题目中的区间是左闭右开,所以差分操作应为f[l]++,f[r]--。不过,在离散化后的处理中,可能需要将原始区间转化为对应的离散索引,并处理为相应的覆盖范围。 然后计算前缀和,当某个位置的前缀和大于0时,说明该区域被覆盖。最后,遍历所有离散化后的点,计算覆盖区间的长度总和。这里需要注意的是,离散化后的点之间的实际距离需要根据原始坐标来计算,而不能直接用离散化后的索引差。 现在,我需要将这些步骤转化为具体的代码实现。首先读取所有区间,收集所有的端点,排序并去重。然后建立映射关系,将原始坐标转换为离散化后的索引。接着初始化差分数组,对每个区间进行差分标记。然后计算前缀和,统计覆盖的区间长度。 需要注意的是,在去重时,可能存在重复的端点,所以需要用集合或者排序后检查相邻元素来去重。另外,在Python中,处理大数组时要注意效率,避免超时。可能需要使用快读或者优化代码结构。 现在检查是否有遗漏的步骤。例如,离散化后的数组中的每个点代表一个坐标,相邻点之间的区间长度是原始坐标的差。因此,在计算总长度时,需要遍历离散化后的数组,对于每个被覆盖的区间,累加上该区间的实际长度。 举个例子,如果离散化后的数组是[x0, x1, x2, ..., xn],那么每个区间[x[i], x[i+1]]对应的原始长度是x[i+1] - x[i]。如果该区间被覆盖(即前缀和大于0),则将其长度加到总和中。 最后,编写代码时要注意数据类型的处理,尤其是当坐标可能达到2^31时,Python的整数没有问题,但要注意运算的正确性。此外,差分数组的大小应等于离散化后的坐标数量,或者可以用字典来处理,但为了效率,通常使用数组和索引。 总结步骤: 1. 收集所有区间的端点,包括左端点和右端点。 2. 对端点进行排序并去重,得到离散化后的坐标数组。 3. 使用二分查找将原始坐标映射到离散化后的索引。 4. 初始化差分数组,对每个区间进行差分标记。 5. 计算前缀和,确定覆盖区域。 6. 遍历离散化后的坐标间隔,累加覆盖区间的长度。 现在需要处理可能的边界情况,比如区间端点重合的情况,或者如何处理开闭区间的问题。根据引用[2],题目中的区间是左闭右开的,所以在处理原始区间时,差分操作在右端点处减1。例如,原区间是[l, r),所以在离散化后的处理中,应该将r对应的索引处减1,或者根据离散化后的点来确定对应的区间。 可能还需要注意离散化后的坐标间隔是否覆盖了所有可能的区域,例如,离散化后的数组中相邻两个点之间的区域是否被正确计算。 现在,编写代码的大致结构: 读取n个区间,保存所有端点到一个列表。 排序并去重端点列表,得到离散化后的坐标。 建立每个坐标到索引的映射(可以用bisect)。 初始化差分数组diff,长度为离散化坐标的数量。 对于每个区间[l, r): 找到l在离散化坐标中的索引i 找到r在离散化坐标中的索引j diff[i] += 1 diff[j] -= 1 计算前缀和数组pre_sum。 遍历每个间隔k(从0到len(离散化坐标)-2): 如果pre_sum[k] > 0: 总长度 += 离散化坐标[k+1] - 离散化坐标[k] 但这里可能存在一个问题,离散化后的坐标数组中的每个点代表的是一个坐标,而区间是左闭右开的,所以在处理时,原始区间的右端点对应的离散化索引可能需要调整。或者,是否在离散化时已经处理了开闭区间的问题? 例如,原区间[l, r),在离散化后的坐标数组中,找到l的位置i,r的位置j,那么对应的覆盖区域是离散化坐标[i]到离散化坐标[j],但每个离散化坐标对应的区间是离散化坐标[k]到离散化坐标[k+1],所以需要确保差分标记正确覆盖这些区间。 或者,是否应该将每个离散化坐标视为一个点,而覆盖区域是离散化后的索引之间的区间? 例如,离散化后的坐标数组为sorted_points,那么对于每个i,区间是sorted_points[i]到sorted_points[i+1]。当原区间[l, r)覆盖了这些区间中的某些部分时,对应的差分标记应在i的位置+1,在j的位置-1,其中j是r对应的索引。 例如,假设原区间为[1,5),而离散化后的坐标数组是[1,3,5]。那么该区间覆盖了1到3和3到5这两个子区间。所以差分在i=0的位置+1,在i=2的位置-1。前缀和计算后,索引0和1的位置的前缀和都是1,所以对应的子区间长度是3-1=2和5-3=2,总长度4,正确。 这说明上述方法是可行的。因此,代码的逻辑是正确的。 现在考虑代码的具体实现。在Python中,可以使用bisect模块的bisect_left函数来找到对应的索引。去重可以通过将列表转换为集合,再排序,或者使用sorted(set(points)),但需要注意这可能会改变顺序,所以正确的做法是先收集所有点,排序后,遍历并去重重复的相邻元素。 例如: points = sorted(all_points) unique_points = [] prev = None for p in points: if p != prev: unique_points.append(p) prev = p 这样得到unique_points是排序并去重后的列表。 然后,差分数组的大小为len(unique_points),或者因为差分数组的索引是从0到len(unique_points)-1,而每个区间对应的是i到j-1?或者需要确认。例如,对于原区间[l, r),离散化后的索引i和j,则对应的覆盖是unique_points[i]到unique_points[j],所以在差分数组中,diff[i] +=1,diff[j] -=1。因为unique_points[j]是r的位置,而区间是左闭右开,所以在计算覆盖时,只需要覆盖到j-1的位置。 这样,前缀和数组pre_sum的长度是len(unique_points),而每个pre_sum[k]表示的是从unique_points[k]开始的区间是否被覆盖。当计算前缀和时,pre_sum[0] = diff[0], pre_sum[1] = pre_sum[0] + diff[1], etc. 然后,对于每个k,如果pre_sum[k] >0,则区间[unique_points[k], unique_points[k+1]]被覆盖,其长度加到总和中。 因此,在遍历时,k的范围是0到len(unique_points)-2,因为每个k对应的是unique_points[k]到unique_points[k+1]。 现在,编写代码: 读取n,然后读取n对l和r,保存到intervals列表。 收集所有端点:all_points = [l for l, r in intervals] + [r for l, r in intervals] 排序并去重得到unique_points。 然后,对于每个区间[l, r),在unique_points中找到i = bisect.bisect_left(unique_points, l) j = bisect.bisect_left(unique_points, r) 然后,diff[i] +=1,diff[j] -=1 计算前缀和,然后遍历每个k,累计长度。 注意,差分数组的长度应该是len(unique_points),初始化为0。然后对于每个区间,找到i和j,并修改diff数组。 最后,遍历k从0到len(unique_points)-2,计算总和。 现在,测试一下这个逻辑是否正确。比如,假设n=1,区间是[1,5),那么all_points是[1,5]。unique_points是[1,5].差分数组初始化为[0,0]. 处理区间后,i=0,j=1,所以diff[0] +=1 → [1,0], diff[1] -=1 → [1,-1]. 前缀和为pre_sum[0]=1, pre_sum[1]=0. 所以k=0时,pre_sum[0]>0,长度是5-1=4,正确。 另一个例子,两个区间[1,3)和[2,5). all_points是1,3,2,5 → 排序后是1,2,3,5 → 去重后是1,2,3,5. 处理第一个区间[1,3): i=0, j=2 → diff[0]+=1, diff[2]-=1 → diff=[1,0,-1,0]. 处理第二个区间[2,5): i=1, j=3 → diff[1]+=1 → diff=[1,1,-1, -1]. 前缀和计算: pre_sum[0] =1 pre_sum[1] =1+1=2 pre_sum[2] =2 + (-1) =1 pre_sum[3] =1 + (-1) =0 然后遍历k=0到2: k=0: pre_sum=1>0 → 长度是2-1=1 k=1: pre_sum=2>0 → 3-2=1 → 总和2 k=2: pre_sum=1>0 →5-3=2 →总和4 总覆盖长度是1+1+2=4,但实际两个区间合并后的覆盖是[1,5),总长度4。正确。 所以这个方法是正确的。 现在,编写Python代码。需要注意的是,在Python中,处理大量数据时,输入方法可能影响速度,但因为n是2e4,Python还是可以处理的。不过,用户提到的引用[4]中提到可能卡时间,需要快读或者优化。这里可能需要使用sys.stdin.readline来读取输入。 代码步骤: 1. 读取n和n个区间。 2. 收集所有端点。 3. 排序并去重。 4. 初始化差分数组。 5. 处理每个区间,找到i和j,更新差分数组。 6. 计算前缀和。 7. 计算总长度。 Python代码示例: import bisect n = int(input()) intervals = [] all_points = [] for _ in range(n): l, r = map(int, input().split()) intervals.append( (l, r) ) all_points.append(l) all_points.append(r) # 排序并去重 all_points.sort() unique_points = [] prev = None for p in all_points: if p != prev: unique_points.append(p) prev = p m = len(unique_points) diff = [0]*(m + 1) # 使用m+1长度,避免j超过范围 for l, r in intervals: i = bisect.bisect_left(unique_points, l) j = bisect.bisect_left(unique_points, r) diff[i] += 1 diff[j] -= 1 # 计算前缀和 pre_sum = [0]*(m) current = 0 total = 0 for k in range(m): current += diff[k] pre_sum[k] = current # 计算总覆盖长度 total_length = 0 for k in range(m-1): if pre_sum[k] > 0: total_length += unique_points[k+1] - unique_points[k] print(total_length) 不过,注意到这里的差分数组被初始化为m+1长度,但实际上,当j等于m时,访问diff[j]会导致索引超出范围。例如,如果unique_points的长度是m,那么索引范围是0到m-1。当r比所有unique_points中的元素都大时,bisect_left返回m,此时访问diff[m]会越界。因此,应该将diff数组初始化为长度为m+1,或者调整处理方式。 比如,将diff数组初始化为长度为m+1,这样j的范围可以到m,不会越界。这样,当j等于m时,diff[j]是存在的。但pre_sum数组的长度是m,而diff数组的长度是m+1,但后面的计算pre_sum只到m-1,所以不影响。 或者,另一种处理方式是使用普通数组,长度为m,但允许j的范围到m,此时在差分数组中,当j >= m时,不做处理,因为后续的前缀和计算不会访问到。不过,这可能需要调整。 例如,在代码中,diff数组初始化为长度为m+1: diff = [0]*(m + 1) 这样,当j的bisect结果最大为m,此时diff[j]是有效的。例如,当r大于所有unique_points的元素时,bisect_left返回m,此时diff[m] -=1。在计算前缀和时,pre_sum的长度是m,所以循环到k in range(m): current += diff[k] → k从0到m-1,而diff[m]不会被访问到。所以这会导致错误,因为j可能等于m,而diff[j]的修改不会被计算到pre_sum中。 例如,假设有一个区间的r是最大的点,比如unique_points = [1,3,5], r=7。bisect_left返回3,此时diff[3] -=1。但pre_sum数组的长度是3,所以计算pre_sum时,k从0到2,而diff[3]没有被处理。这将导致错误。 这说明之前的代码逻辑有问题。正确的做法应该是将差分数组的长度设置为m,并且在处理j时,如果j >= m,则不做处理,或者确保j不超过m-1。但这显然不正确,因为r可能比所有离散化后的点都大。 这说明需要调整离散化的处理方式。或许,应该将所有的端点都包含在离散化后的unique_points中,这样任何区间的r必然存在于unique_points中?但是原始问题中,用户输入的区间可能有任意的r,不一定出现在其他区间的端点中。例如,如果输入的区间是[1,2),[3,4),那么unique_points是[1,2,3,4]. 如果一个新区间的r=5,那么bisect_left返回4,此时diff数组的长度应该是5,这样j=4不会越界。 所以,正确的做法是将diff数组初始化为长度为m,其中m是unique_points的长度。但此时,如果j等于m,那么diff[j]越界。因此,如何处理这种情况? 可能问题出在离散化的步骤。因为所有区间的端点已经被收集到all_points中,所以r必然是其中一个点吗?不,比如,如果有一个区间的r是某个中间值,比如原始区间是[1,5),另一个区间是[3,7),那么all_points包括1,5,3,7。排序去重后是1,3,5,7。所以对于任何输入的区间端点,必然在unique_points中。因为所有输入的l和r都被加入all_points,然后排序去重后的unique_points包含所有端点。因此,对于任何输入的l或r,bisect_left在unique_points中会找到对应的位置,而不会出现r不在unique_points中的情况。因此,j的bisect结果最大为m-1,当r是unique_points的最后一个元素时,bisect_left返回m-1。因此,diff数组的长度设为m即可,不会越界。 因此,之前的代码中的错误是,在收集all_points时,已经将每个区间的l和r都添加进去,所以unique_points中必然包含所有的l和r。因此,在bisect查找时,r一定存在于unique_points中吗?不一定,例如,如果一个区间的r是某个中间值,比如输入的区间为[1,4),而另一个区间的端点是3和5。则all_points包含1,4,3,5 →排序去重后为1,3,4,5。那么原区间的r=4,存在于unique_points中。所以任何输入的区间的端点都会被包含在unique_points中,因此bisect查找r时,一定能找到对应的位置,即在unique_points中存在,因此j的bisect结果不会超过m-1。 因此,diff数组的长度应为m,初始化为diff = [0]*m。这样,对于每个区间的r,bisect_left返回j在0到m-1范围内,不会越界。 修改代码中的diff数组初始化: diff = [0]*m 然后,对于每个区间: i = bisect.bisect_left(unique_points, l) j = bisect.bisect_left(unique_points, r) diff[i] +=1 diff[j] -=1 这样,当处理区间[1,5)时,如果unique_points中存在5,则j是正确的。 现在,重新测试之前的例子: 例子1:n=1,区间[1,5)。all_points = [1,5], unique_points=[1,5], m=2. i=0, j=1. diff[0] +=1 → diff[0]=1, diff[1] -=1 → diff[1]=-1. 计算pre_sum: pre_sum[0] =1 pre_sum[1] =1 + (-1) =0 遍历k from 0 to m-2=0: k=0: pre_sum[0] >0 →长度是5-1=4 →正确。 另一个例子,n=2,区间[1,3)和[2,5). all_points包括1,3,2,5 →排序去重后为1,2,3,5,m=4. 处理第一个区间[1,3): i = bisect.bisect_left([1,2,3,5], 1) →0 j = bisect.bisect_left([1,2,3,5],3) →2 diff[0] +=1 →diff[0]=1 diff[2] -=1 →diff[2]=-1 处理第二个区间[2,5): i = bisect_left([1,2,3,5],2) →1 j = bisect_left([1,2,3,5],5) →3 diff[1] +=1 →1 diff[3] -=1 →-1 现在diff数组是[1,1,-1,-1]. 计算pre_sum: pre_sum[0] =1 pre_sum[1] =1+1=2 pre_sum[2] =2 + (-1)=1 pre_sum[3] =1 + (-1)=0 遍历k=0到2: k=0: pre_sum[0]=1>0 →长度2-1=1 k=1: pre_sum[1]=2>0 →3-2=1 k=2: pre_sum[2]=1>0 →5-3=2 总和4,正确。 这说明代码逻辑正确。现在,代码中的diff数组应该初始化为长度m,而不是m+1。 修正后的代码: import bisect n = int(input()) intervals = [] all_points = [] for _ in range(n): l, r = map(int, input().split()) intervals.append( (l, r) ) all_points.append(l) all_points.append(r) # 排序并去重 all_points.sort() unique_points = [] prev = None for p in all_points: if p != prev: unique_points.append(p) prev = p m = len(unique_points) diff = [0] * m # 修正为m长度 for l, r in intervals: i = bisect.bisect_left(unique_points, l) j = bisect.bisect_left(unique_points, r) diff[i] += 1 diff[j] -= 1 # 计算前缀和 current = 0 total_length = 0 for k in range(m): current += diff[k] if current > 0 and k < m -1: # 因为每个k对应区间[k, k+1) total_length += unique_points[k+1] - unique_points[k] print(total_length) 这样,在计算前缀和的同时,直接判断current是否大于0,并且k小于m-1,这样避免使用pre_sum数组,节省空间。同时,遍历时直接累计长度。 例如,当current在k处被更新后,判断current是否大于0,如果是,则累加k到k+1的区间长度。因为每个diff[k]的修改影响的是从unique_points[k]开始的区间。 这样,代码更高效,因为不需要存储pre_sum数组,而是在遍历时直接处理。 测试这个修正后的代码是否工作。例如,之前的例子: 对于第一个例子,n=1,区间[1,5),unique_points是[1,5]. diff数组初始化为[0,0]. 处理后diff为[1,-1]. current从0开始: k=0: current +=1 →1>0,且k<1,累加5-1=4 →total=4. k=1: current +=-1 →0,不累加. 最终输出4,正确。 另一个例子,两个区间,处理后的diff是[1,1,-1,-1].遍历: k=0: current +=1 →1>0 →累加2-1=1 →total=1. k=1: current +=1 →2>0 →累加3-2=1 →total=2. k=2: current +=-1 →1>0 →累加5-3=2 →total=4. k=3: current +=-1 →0 →不处理. 输出4,正确。 这说明修正后的代码是正确的。 因此,最终的代码实现应该如上述所示,使用离散化和差分的方法,结合前缀和的计算,在遍历时直接累加覆盖区间的长度。</think>### P1496 火烧赤壁离散化算法实现步骤 #### 核心思路 离散化处理用于将大范围稀疏坐标转换为紧凑的连续索引,结合差分数组标记区间覆盖情况,最终通过前缀和统计覆盖总长度[^2][^5]。 #### 代码实现 ```python import bisect n = int(input()) intervals = [] all_points = [] for _ in range(n): l, r = map(int, input().split()) intervals.append((l, r)) all_points.extend([l, r]) # 离散化:排序并去重 all_points.sort() unique_points = [] prev = None for p in all_points: if p != prev: unique_points.append(p) prev = p m = len(unique_points) diff = [0] * m # 差分数组 # 处理每个区间的覆盖标记 for l, r in intervals: i = bisect.bisect_left(unique_points, l) j = bisect.bisect_left(unique_points, r) diff[i] += 1 if j < m: # 防止越界(实际不会发生) diff[j] -= 1 # 计算覆盖总长度 current = 0 total = 0 for k in range(m): current += diff[k] if current > 0 and k < m - 1: total += unique_points[k+1] - unique_points[k] print(total) ``` #### 关键点解析 1. **离散化处理** - 收集所有区间端点并去重排序,建立坐标到索引的映射[^3][^4] - 使用二分查找(`bisect_left`)快速定位离散化后的索引 2. **差分数组** - 对每个区间左端点`+1`,右端点`-1`,标记覆盖变化 - 例如区间$[l_i, r_i)$对应`diff[i]++`和`diff[j]--` 3. **前缀和计算** - 遍历差分数组时实时计算当前覆盖层数(`current`) - 若当前层数>0,累加相邻离散点间距作为覆盖长度[^5] #### 复杂度分析 - 时间复杂度:$O(n \log n)$(离散化排序和二分查找占主导) - 空间复杂度:$O(n)$(存储离散化坐标和差分数组) #### 注意事项 - 区间端点必须全部加入离散化列表,否则二分查找映射错误 - 差分数组长度与离散化坐标数一致,避免索引越界 - 差分标记需严格处理为左闭右开区间逻辑
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值