题意
首先给出操作次数n,然后每次进行三种操作中的一种,1:push x, 2:pop, 3:取mid。也就是1将x压入栈顶,2弹出栈顶元素并输出,输出栈元素的中位数(若元素个数为偶数则取较小值(n / 2))。
思路1 - 二分法
- 通过一个vector模拟栈,插入删除时间复杂度O(1)
- 难点在找中位数,第一个想法是用map记录出现次数,然后根据map顺序的性质,从小到大顺序数,TLE17分。
- 找到一个很妙的解法(解法1),思路是定义一个全局变量mid记录中位数,每次插入、删除的时候更新mid,peekmid的时候直接输出。
- 更新操作的时间复杂度为 O ( l o g 2 n ) O(log_2n) O(log2n):通过两个几乎等长的multiset记录stack的前半段和后半段。保证前半段长度大于等于后半段,长度差最大为1。每次取mid = 前半段最后一个元素.
思路2 - 线段树
柳神的线段树(解法2)没看懂,过两天抽空在acwing上学。
总结
- multiset的用法:
multiset.erase(int x)的时间复杂度是 O ( k ⋅ l o g 2 n ) O(k \cdot log_2n) O(k⋅log2n),找到第一个x并依次删除后续x,底层是红黑树。
multiset.erase(iterator x)的时间复杂度是 O ( l o g 2 n ) O(log2_n) O(log2n),随机找到要删的元素,并删除、调整。 - iterator 只能++、–;
解法1 - 二分
#include<bits/stdc++.h>
using namespace std;
int n, mid;
vector<int> rec;
multiset<int> l, r;
void sort(){
if(l.size() < r.size()){
l.insert(*r.begin());
r.erase(r.begin());
}
else if(l.size() > r.size() + 1){
r.insert(*(--l.end()));
l.erase(--l.end());
}
if(l.size()) mid = *(--l.end());
}
int main(){
cin>>n;
while(n--){
string input; cin>>input;
if(input == "Pop"){
if(!rec.size()) puts("Invalid");
else{
int t = rec.back();
printf("%d\n", t);
t <= mid ? l.erase(l.find(t)) : r.erase(r.find(t));
rec.pop_back();
sort();
}
}else if(input == "Push"){
int b; scanf("%d", &b);
rec.push_back(b);
(!l.size() || b <= mid) ? l.insert(b) : r.insert(b);
sort();
}else
!rec.size() ? puts("Invalid") : printf("%d\n", mid);
}
return 0;
}
解法2 - 线段树
#include <cstdio>
#include <stack>
#define lowbit(i) ((i) & (-i))
const int maxn = 100010;
using namespace std;
int c[maxn];
stack<int> s;
void update(int x, int v) {
for(int i = x; i < maxn; i += lowbit(i))
c[i] += v;
}
int getsum(int x) {
int sum = 0;
for(int i = x; i >= 1; i -= lowbit(i))
sum += c[i];
return sum;
}
void PeekMedian() {
int left = 1, right = maxn, mid, k = (s.size() + 1) / 2;
while(left < right) {
mid = (left + right) / 2;
if(getsum(mid) >= k)
right = mid;
else
left = mid + 1;
}
printf("%d\n", left);
}
int main() {
int n, temp;
scanf("%d", &n);
char str[15];
for(int i = 0; i < n; i++) {
scanf("%s", str);
if(str[1] == 'u') {
scanf("%d", &temp);
s.push(temp);
update(temp, 1);
} else if(str[1] == 'o') {
if(!s.empty()) {
update(s.top(), -1);
printf("%d\n", s.top());
s.pop();
} else {
printf("Invalid\n");
}
} else {
if(!s.empty())
PeekMedian();
else
printf("Invalid\n");
}
}
return 0;
}
题目
Stack is one of the most fundamental data structures, which is based on the principle of Last In First Out (LIFO). The basic operations include Push (inserting an element onto the top position) and Pop (deleting the top element). Now you are supposed to implement a stack with an extra operation: PeekMedian – return the median value of all the elements in the stack. With N elements, the median value is defined to be the (N/2)-th smallest element if N is even, or ((N+1)/2)-th if N is odd.
Input Specification:
Each input file contains one test case. For each case, the first line contains a positive integer N (≤105). Then N lines follow, each contains a command in one of the following 3 formats:
Push key
Pop
PeekMedian
where key
is a positive integer no more than 105.
Output Specification:
For each Push
command, insert key
into the stack and output nothing. For each Pop
or PeekMedian
command, print in a line the corresponding returned value. If the command is invalid, print Invalid
instead.
Sample Input:
17
Pop
PeekMedian
Push 3
PeekMedian
Push 2
PeekMedian
Push 1
PeekMedian
Pop
Pop
Push 5
Push 4
PeekMedian
Pop
Pop
Pop
Pop
Sample Output:
Invalid
Invalid
3
2
2
1
2
4
4
5
3
Invalid