今天主要学习了STL以及常用技巧、单调栈、单调队列、并查集。
1.set和multiset
set自带排序(默认优先级由小到大,可以重载<来改变排序),无重复
遍历
int::iterator pos;
for (pos = coll1.begin(); pos != coll1.end(); ++pos)
{
cout << *pos << ' ';
}
mutiset:多重集合 和set最大的区别就是,它可以插入重复的元素,如果删除的话,相同的也一起删除了;
如果查找的话,返回该元素的迭代器的位置,若有相同,返回第一个元素的地址;
其他使用和set基本类似。
2.unique(地址,地址) (头文件<algorithm>)
unique的作用是“去掉”容器中相邻元素的重复元素,它实质上是一个伪去除,它会把重复的元素添加到容器末尾,而返回值是去重之后的尾地址,使用前要先排序
unique(V.begin(),V.end());(想要的到去重后元素个数可以减去V.begin())
3.next_permutation(下一个排列),prev_permutation(上一个排列)(头文件<algorithm>)
next_permutation()会取得[first,last)所标示之序列的下一个排列组合,如果没有下一个排列组合,便返回false;否则返回true。
简单应用:输出序列{1,2,3,4}字典序的全排列。
int ans[4]={1,2,3,4};
do
{
for(int i=0;i<4;++i)
cout<<ans[i]<<" ";
cout<<endl;
}while(next_permutation(ans,ans+4));
4.并查集
这个直接来一道题
给定n(n<=1e5)个数a1,a2,a3...an(0≤ai≤1e9),现在有n个操作,每次把位置p的数字删掉,求每次删除前,最大子段和,区间不能包含已删除的位置。
eg:a: 1 3 2 5
删除位置3的元素,得到1 3 _ 5,所以最大子段和为5
删除位置4的元素,得到1 3 _ _, 所以最大子段和为4
删除位置1的元素,得到_ 3 _ _,所以最大子段和为3
删除位置1的元素,得到_ _ _ _,所以最大子段和为0
做法一:用set正着模拟操作,一开始的最大子段是整个区间,每次我们要删除一个元素,就取出对应的区间,删掉对应元素,将他拆成两个子段和,再塞回set里。取出对应区间直接用lowerbound即可,把前缀和预处理一下,就可以,通过区间的左右端点求出新的区间。我再用一个优先队列,维护当前这些区间最大和,把新的区间也塞进优先队列里。取最值时注意,如果set里找不到这个子段,说明它已经被删了,继续pop优先队列即可。
做法二:将删除操作存起来,倒着加入,用并查集维护区间的连续性。加一个数字,影响的只有他左右两个连续区间的和,用左右的连续区间的值,加上这个位置的值,就是合并后连续区间的和。合并值之前先把答案最大值存下来,合并之后,也要更新一下最大子段和,时间优秀很多。
其他做法?线段树单点修改,维护最大子段和。
5.单调栈
利用单调栈,可以找到从左/右遍历第一个比它小/大的元素的位置
总结:一个元素向左遍历的第一个比它小的数的位置就是将它插入单调栈时栈顶元素的值,若栈为空,则说明不存在这么一个数。然后将此元素的下标存入栈,就能类似迭代般地求解后面的元素。
代码:
从左遍历找第一个比a[i]小的元素的位置(下标)
stack<int> s;
for(int i=0 ;i<n ;i++){
while(s.size() && a[s.top()] >= a[i]) s.pop();
if(s.empty()) l[i] = 0;
else l[i] = s.top();
s.push(i);
}
数组模拟
int top=0,j;
int st[10]; //模拟栈
st[0]=0;
for(j=0;j<n;j++){
while(top && h[st[top]]>=h[j])top--;//单调栈维护边界
le[j]=st[top];//左边界
st[++top]=j;//入栈
}
从右遍历找第一个比a[i]小的元素的位置(下标)
for(int i=n-1 ;i>=0 ;i--){
while(s.size() && h[s.top()] >= h[i]) s.pop();
if(s.empty()) R[i] = i;
else R[i] = s.top();
s.push(i);
}
top=0;
st[0]=m+1;
for(j=m;j;j--){
while(top && h[st[top]]>=h[j])top--;
re[j]=st[top]-1;
st[++top]=j;
}
应用:
首先考虑最大面积的矩形X的左右边界的性质:
设其左边界为L,右边界为R,则其高H = min{h[i] | L <= i <= R}
左边界L是满足h[j-1] < h[i]的最大的j,即从i点向左遍历的第一个高度比i小的点的右边一个点
而右边界R是满足 h[j+1] < h[i]的最小的j,即从i点向右遍历第一个高度比i小的点的左边一个点
ans=0;
for(int i=0 ;i<n ;i++){
ans = max(ans,h[i] * (R[i] - L[i]));
}
6.单调队列
单调队列可用于求区间的最小值
先来道题
给定一个一定长度的窗口,他从左到右移动,需要维护每个窗口内元素的最大值。(POJ2823)
[1 3 -1] -3 5 3 6 7 : max = 3
1 [3 -1 -3] 5 3 6 7 : max = 3
1 3 [-1 -3 5] 3 6 7 : max = 5
1 3 -1 [-3 5 3] 6 7 : max = 5
1 3 -1 -3 [5 3 6] 7 : max = 6
1 3 -1 -3 5 [3 6 7] : max = 7
暴力o(n^2), 优先队列o(nlogn), 还想更优o(n)?
维护队首元素作为答案,去除多余元素
队列里每个元素用二元组(id,val)表示,每次从队尾入队删除无用元素,保证编号递增,值递减,查询区间的左右端点可以向左移或不动
eg: 1 3 -1 -3 5 3 6 7
[1,1]: (1,1)
[1,2]: (1,1),(2,3)
[1,3]: (2,3),(3,-1)
[2,4]: (2,3),(3,-1),(4,-3)
[3,5]: (2,3),(3,-1),(4,-3),(5,5)
[4,6]: (5,5),(6,3)
单调队列优化dp,
形如 f[i] = min( f[j] | Low[i]<= j <=Up[i] ) + a[i] (eg.最大字段和)
Low 和 Up 是关于i的单调不降函数