P8551 Bassline 题解
分析
这道题做月赛的时候想了好久,最后发现其实很简单。
我们用样例数据来分析:
如图所示,我们将每个区间抽象化为一个一个的长条。题目给我们的两个要求可以理解为 [ x , y ] [x,y] [x,y] 所覆盖的列中没有任意一个长条开始或结束,如图中第 4 列就是绿色长条的开始部分,第 7 列就是绿色长条的结束部分,它们都不满足要求,所以 [ x , y ] [x,y] [x,y] 不能包含这两列。
理解到这里了,这道题就很简单了:我们只需要用有长条开始或者结束的列将最大区间分成几个部分(如下图),取区间长度减一乘以区间内的长条数的最大值即 k − ( y − x ) k-(y-x) k−(y−x) 就行了。
思路
我们使用一个数组存储每一个数(即上面分析时所说的列)是多少个区间的起点和终点,我用的是结构体 x[i]
,当然你也可以用两个数组解决。对于读入的每个区间
[
l
i
,
r
i
]
[l_i,r_i]
[li,ri] ,我们让 x[li].geb++,x[ri].end++
,并且存储最左边的点 left
和最右边的点 right
。
接着,我们从 left
循环到 right
,用 cnt
来记录能被选择的
[
x
,
y
]
[x,y]
[x,y] 区间中的元素个数(即
y
−
x
y-x
y−x ),每当遇到一个区间的开始或结束就将 cnt
清零,再用 k
记录每一个数上的区间个数,每次用 cnt*k
跟答案比较取最大值就好了。
程序
#include<bits/stdc++.h>
using namespace std;
int n;
struct Node
{
int beg=0,end=0;
}x[300005];//使用结构体存储每个数字是多少个区间的起点和终点
int main()
{
scanf("%d",&n);
int left=300005,right=-1,k=0,cnt=0;
for(long long i=1;i<=n;i++)
{
int l,r;
scanf("%d%d",&l,&r);
x[l].beg++,x[r].end++;//区间[l,r]开头和结尾的数字的起点个数和终点个数分别+1
left=min(l,left);
right=max(right,r);
}
long long ans=0;//一定记得开long long,不然最后一个点会炸
for(int i=left;i<=right;i++)
{
++cnt;//记录这个可选区间中y-x的值
if(x[i].beg || x[i-1].end)
cnt=0;//如果i是某个区间的起点或者终点就将cnt清零
k+=x[i].beg;//用k记录i上的区间个数
ans=max(ans,cnt*k);//取最优解
k-=x[i].end;
}
printf("%lld\n",ans);
}