CCF 201809-2 买菜

问题描述

  小H和小W来到了一条街上,两人分开买菜,他们买菜的过程可以描述为,去店里买一些菜然后去旁边的一个广场把菜装上车,两人都要买n种菜,所以也都要装n次车。具体的,对于小H来说有n个不相交的时间段[a1,b1],[a2,b2]...[an,bn]在装车,对于小W来说有n个不相交的时间段[c1,d1],[c2,d2]...[cn,dn]在装车。其中,一个时间段[s, t]表示的是从时刻s到时刻t这段时间,时长为t-s。
  由于他们是好朋友,他们都在广场上装车的时候会聊天,他们想知道他们可以聊多长时间。

输入格式

  输入的第一行包含一个正整数n,表示时间段的数量。
  接下来n行每行两个数ai,bi,描述小H的各个装车的时间段。
  接下来n行每行两个数ci,di,描述小W的各个装车的时间段。

输出格式

  输出一行,一个正整数,表示两人可以聊多长时间。

样例输入

4
1 3
5 6
9 13
14 15
2 4
5 7
10 11
13 14

样例输出

3

数据规模和约定

  对于所有的评测用例,1 ≤ n ≤ 2000, ai < bi < ai+1,ci < di < ci+1,对于所有的i(1 ≤ i ≤ n)有,1 ≤ ai, bi, ci, di ≤ 1000000。

分析:

本题去年在考的时候只有80分,现在再做一遍明白当时是超时了,n最大1000,两千个区间,每个区间最大长度可能是一百万,最坏情况可能要考虑十亿个数。去年做的时候花了近半小时,在纸上画各种区间相交,排序再各种条件判断,容易出错或者超时,现在做的时候决定摒弃常规思路。虽然是区间问题但是用线段树或者树状数组就有点小题大作了,采用离散化或者哈希映射便能很简洁的解决该问题。(如果不想看复杂的思考过程可以跳过思路一)

思路一:

不区分ab和cd,只要遇见一个区间,立刻将区间上所有数加一,最后统计数组上所有值为2元素的个数。很明显,这种思路最大的问题是区间长度并不能很好的转化为元素的个数,因为比如两个区间相交于[2,4],可以发现a[2]-a[4]上三个数都为2,而区间长度为2,比元素个数少1。想到的解决办法是比如[1,4]和[2,8],在遍历2-8时给下标是2-4都加上1,一旦遍历到a[5]!=2,立刻ans--,而且在另一种情况[1,4]和[2,4]相交,由于从2遍历到4都不能遍历到值不为2的元素,用标志变量可解决该问题。可以看见这种思路一方面没有用离散化,开了大数组,而且十来行的代码思考出来上面的情况也不容易;另一方面更为致命,就是这种思路本质是错的,比如第一个人有区间[1,3]和[5,9],第二个人有区间[3,5],执行这种算法ans会加一,然而他们并未相交。所以思路一行不通。

思路二:

由于直接哈希,用区间元素值为2的个数不能表示区间相交的长度,所以需要一种全新的思路。可以发现,[1,3]区间长度为2,而相邻两个数的中点也就是1.5和2.5个数也为2,我们可以将之前的哈希向右平移0.5,会发现,之前所有的情况都不要考虑了,任意两个相交的区间中相邻两个数中点映射的值为2的个数,也就是所求问题答案。例:[1,4]和[2,8] 中2.5和3.5值为2,而[2,4]长度也为2,思路一的其它例子也完美的符合这个规律。现在只需要将double映射为元素个数,采用unordered_map<double,int>。于是我们可以得到下面这个超级简洁的代码。

#include <iostream>
#include <unordered_map>
using namespace std;
unordered_map<double,int> m;
int main(){
	int n,a,b;
	cin>>n;
	long long ans = 0;
	for(int i = 0;i < 2 * n;i++){
		cin>>a>>b;
		for(int j = a;j < b;j++){
			double k = j + 0.5;
			m[k]++;
			if(m[k] == 2)	ans++;	
		}	
	}
	cout<<ans;
	return 0;
}

不到20行便解决了该问题,至于为什么会有思路三的出现是因为上面代码提交只有90分,超时了,正如文章开头所讲,用哈希,差不多在最坏情况下给十亿个数加一了。于是我对思路二进行了改进,开始想到的思路对区间[a,b]中所有元素++,复杂度为O(b-a),要想区间修改复杂度降到O(1),容易想到线段树的标记延迟下传,但是如果使用线段树,思维量可能超过了一开始的直接排序后if else分类讨论了。

思路三:

由于思路二代码只能得90分,可能只有最后一两组样例超时,于是想到对一个循环而言,里面的基本语句多一条,复杂度虽然是一个量级的,但是时间可能翻倍了。于是考虑“剪枝”。对比上面代码可以发现,对于第一个人输入的n个区间,我们m[k]++没问题,但是对于第二个人输入的n个区间,我们先m[k]++再判断m[k]是否等于2显得有点多余,于是我将一次循环拆分为两次,并且尽可能的将k的定义放在外面,防止循环一次就重新给k分配一次空间。100分代码如下:

#include <iostream>
#include <unordered_map>
using namespace std;
unordered_map<double,int> m;
int main(){
	int n,a,b;
	double k;
	cin>>n;
	long long ans = 0;
	for(int i = 0;i < n;i++){
		cin>>a>>b;
		for(int j = a;j < b;j++){
			k = j + 0.5;
			m[k]++;	
		}	
	}
	for(int i = 0;i < n;i++){
		cin>>a>>b;
		for(int j = a;j < b;j++){
			k = j + 0.5;
			if(m[k] == 1)	ans++;	
		}
	}
	cout<<ans;
	return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_30277239/article/details/87803829