UVA1471 防线 Defense Lines
一、前言
这是一道非常好的运用数据结构优化的题目,本篇题解将详细讲解解题思路,一是给做这题的人提供想法,二是让自己更好地掌握这种方法。
二、题意
在一个长度为
三、思路
- 首先看这道题目所要求的操作:删去一个连续子序列,得到最长连续递增子序列。如图:
显然,在删去中间的连续子序列之后,得到的最长连续递增子序列是由两个序列合并在一起的得到的,因此我们需要两个数组:一个
-
接下来我们再从熟悉的地方入手,对于最长递增子序列的长度,我们已经见过多次,可以使用 DP 的方式求出,具体方法可以参考P1020 [NOIP1999 普及组] 导弹拦截,不过这道题要求的是连续的序列,会略有些不同。(似乎更简单?)
-
然后是本题最难也最重要的地方:如何来维护删去一个区间并更新答案的操作。最容易想到的方法就是枚举区间的左端点
l 和右端点l 同时更新答案,时间复杂度为O(n^2) ,由于本题的数据范围n \leq 200000 ,显然是会超时的。对于这样的数据范围,一般是用O(n) 或是O(nlogn) 的方法做。(当然若数据范围再大些,就需要用更优的方法做) -
接下来我们继续观察题目并思考:如何才能够减少不必要的计算?因为我们枚举
i 和j 时枚举了所有的情况,但实际上有一些情况是绝对不可能成为答案的,也就是说没有了保留价值,如果我们能够减少对这些没有意义的情况的计算,只存下有保留价值的值,就可以优化计算。(其实思想类似于搜索中的剪枝) -
那么我们现在的任务很明确了:找到哪些值有保留价值,哪些值没有意义。举个例子看看(
a[i] 表示数值):
| 4 | 6 | ||||||||
|---|---|---|---|---|---|---|---|---|---|
| 2 | 1 |
我们可以发现,对于数值
- 那么如何来维护这些信息呢? set。将
a[i] 和ed[i] “打包” 成一个结构体,再建立一个 set,在插入一个数时,二分查找小于它的最大的值,与其比较,判断新插入的值有没有保留价值,若没有就算了,有的话还要再判断它后面的值有没有保留价值。具体操作在下面的知识点和代码中可见。
四、知识点
set 的一些操作
-
set 中的元素是有序的,且有去重性。
-
定义一个 set 的迭代器(类似于指针)。
set<node>::iterator it;
- 将一个迭代器转化为可以用的形式。
node last=*it;
- set 中的迭代器只能自加或自减,代表指向上一个/下一个元素。
it++;
- set 自带二分查找函数,可以在 set 内部查找,返回迭代器。
set<node>::iterator it =s.lower_bound(now);
五、代码
#include<cstdio>
#include<set>
using namespace std;
const int N=2e5+7;
int t,n,a[N],ed[N],st[N],ans;
bool flag;
struct node//定义结构体存储a和ed
{
int a,ed;
node(int a,int ed)://构造函数,为结构体赋值
a(a),ed(ed){}
bool operator < (const node &x) const//重载运算符
{
return a<x.a;
}
};
set<node>s;//set维护序列
void pre()//预处理出st和ed数组
{
//ed[i]是以i结束的最长连续递增子序列长度
ed[1]=1;
for(int i=2;i<=n;i++)
if(a[i]>a[i-1])
ed[i]=ed[i-1]+1;
else
ed[i]=1;
//st[i]是以i开始的最长连续递增子序列长度
st[n]=1;
for(int i=n-1;i>=1;i--)
if(a[i]<a[i+1])
st[i]=st[i+1]+1;
else
st[i]=1;
}
int main()
{
scanf("%d",&t);
while(t--)
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
pre();
s.clear();
s.insert(node(a[1],ed[1]));
ans=1;
for(int i=2;i<=n;i++)
{
node now(a[i],ed[i]);//由于结构体里定义了构造函数,这里可以直接赋值
set<node>::iterator it =s.lower_bound(now);//二分查找第一个>=now的值
flag=true;//flag为true表示新加入的元素可以保留,反之要删去
if(it!=s.begin())
{
node last=*(--it);//last为满足a[j]<a[i]最大的a[j]
ans=max(ans,st[i]+last.ed);//更新答案
if(now.ed<=last.ed)//如果last排在now前面,且now的ed比last小,那now就没有保留价值了
flag=false;
}
if(flag)
{
s.erase(now);//如果说新插入的元素可以保留,说明它 ed 一定比老的要大,重新更新位置
s.insert(now);
it=s.find(now);
it++;
while(it != s.end() && (*it).a > now.a && (*it).ed <= now.ed)//删去后面没有保留意义的元素
s.erase(it++);
}
}
printf("%d\n",ans);
}
return 0;
}
六、后记
-
仔细品味这题,能看出所说复杂了些,但思路理清后还是简单的,重点是如何用数据结构来优化方法,这种做法十分值得学习,因此写篇题解记录下来。
-
以上内容是在参考刘汝佳的《算法竞赛入门竞赛》的相关内容,并在自己的理解下写出的。