问题描述
小明这些天一直在思考这样一个奇怪而有趣的问题:
在1~N的某个全排列中有多少个连号区间呢?这里所说的连号区间的定义是:
如果区间[L, R] 里的所有元素(即此排列的第L个到第R个元素)递增排序后能得到一个长度为R-L+1的“连续”数列,则称这个区间连号区间。
当N很小的时候,小明可以很快地算出答案,但是当N变大的时候,问题就不是那么简单了,现在小明需要你的帮助。
输入格式
第一行是一个正整数N (1 <= N <= 50000), 表示全排列的规模。
第二行是N个不同的数字Pi(1 <= Pi <= N), 表示这N个数字的某一全排列。
输出格式
输出一个整数,表示不同连号区间的数目。
样例输入1
4
3 2 4 1
样例输出1
7
样例输入2
5
3 4 2 5 1
样例输出2
9
分析:
我相信很多同学读完题后又回去反复看了很多遍关于“连号区间”的定义,并不断结合样例数据在思考,但是依然没懂起“连号区间”的意思(这不怪同学们的理解能力,确实是题目中的那个释义太差劲了)。
所以在这里我再解释一下什么叫“连号区间”。
比如一组数:2 4 3 5 1
当看第一个数{ 2 } 的时候,这个区间只有一个数 [2],显然满足连续递增的条件,因此是“连号区间”;
当看前两个数{ 2 4 }的时候,我们把这个区间中的所有数递增排序得到 [2,4],发现其并不满足连续递增的条件(2与4之间缺少了数字3),因此不是“连号区间”;
当看前三个数{ 2 4 3 }的时候,我们把这个区间中的所有数递增排序得到 [2,3,4],发现其满足连续递增的条件,因此是“连号区间”;
当看前四个数{ 2 4 3 5 }的时候,我们把这个区间中的所有数递增排序得到 [2,3,4,5],发现其满足连续递增的条件,因此是“连号区间”;
当看所有数{ 2 4 3 5 1 }的时候,我们把这个区间中的所有数递增排序得到 [1,2,3,4,5],发现其满足连续递增的条件,因此是“连号区间”;
以上就是关于“连号区间”的定义了
接下来我们来看一下“连号区间”所满足的特点。可以很容易地看出,对于一个连号区间而言,其中最大的那个数减去最小的那个数其实刚好就等于这中间的所有数字的总量再减去1。即:
max - min = size_of_section - 1(size_of_section表示某个区间内的数字的总数) ①
如果我们把输入的N个数字的全排列放进一个数组arr中,如:arr[]={2,4,3,5,1}
那么我们表示某个区间内的数字数量的方法就是(假设有两个指针i,j分别指向区间的两端):
size_of_section = j - i + 1 ②
验证:例如对于arr[1]=4 , arr[3]=5,即标记了区间 2 4 3 5 1(红色部分),显然这个区间内有三个数4,3,5,则size_of_section = j - i + 1 = 3 - 1 + 1 = 3
此时我们再把②代入①中就可以得到:
max - min = j - i
这个公式是本题中最关键的一步
而整个解题过程是:
首先将题目所给的某个全排列放进一个一维数组中
然后利用一个二重循环遍历该数组中的全部区间,对于其中的每个区间,都通过上述公式进行判断,如果满足条件则说明该区间是连号区间,统计;否则不予统计
最后输出结果
下面直接给出本题的完整代码:
#include
using namespace std;
const int MAX=50010;
int arr[MAX];
int main()
{
int n,max,min,ans=0;
cin>>n;
for(int i=0;i<n;i++) cin>>arr[i];
for(int i=0;i<n;i++)
{
max=0,min=MAX; //初始化最大值与最小值
for(int j=i;j<n;j++){
max=arr[j]>max?arr[j]:max; //检测最大值是否变化(并修改)
min=arr[j]<min?arr[j]:min; //检测最小值是否变化(并修改)
if(max-min == j-i) ans++; //max-min == j-i 则意味着是连号区间
}
}
cout<<ans<<endl;
return 0;
}