【PAT-A1057】Stack(找第序列第K大,分块法or树状数组+二分)

【题意】

给栈stack加一个除了push和pop外的操作peekMedian获取中位数。

【分析】

值得注意的是,由于这里是动态查询,即在每次peekMedian之间序列可能会由于PUSH和POP操作变化,所以用简单的排序后直接获取操作复杂度会很大,因为每次序列变化后都要重新排序。
而动态查询序列第K大问题通常采用分块法O(n√n)或者树状数组+二分的方法O(logn*logn)显然通常情况后者复杂度更低,但是实现较复杂一些。

【代码】

分块法

分的块数为元素可能取到的最大值+1再开根号,10^5+1开根号再向上取整就是317记住这个数,如果具体题目N换了就自己算一下。

  • 还有就是这里可以把三种情况的代码用函数封装一下,清楚一些。
#include 
#include 
#include 
#include 
using namespace std;
const int maxb = 317;
const int maxn = 100010;
int block[maxb];
int hashTable[maxn]; // 存对应元素的的数量
int main() {
	// 初始化
	memset(block, 0, sizeof(block));
	memset(hashTable, 0, sizeof(hashTable));
	// 输入
	int n, cnt = 0, e;
	char op[12];
	stack<int> st;
	scanf("%d", &n);
	for (int i = 0; i < n; i++)
	{
		scanf("%s", op);
		if (!strcmp(op, "PeekMedian")) {
			
			if (cnt == 0) {
				printf("Invalid\n");
			}
			else {
				int median;
				if ((cnt & 1) == 0) {
					median = cnt >> 1;
				}
				else
				{
					median = (cnt + 1) >> 1;
				}
				int tmp = 0;
				for (int j = 0; j < maxb; j++)
				{
					if(tmp + block[j] < median) {
						tmp += block[j];
					}
					else
					{
						int num = j * 316; // 第j号块的第一个数
						while (tmp + hashTable[num] < median)
						{
							tmp += hashTable[num++];
						}
						printf("%d\n", num);
						break;
					}
				}
			}
		}
		else if (!strcmp(op, "Pop")) {
			if (cnt == 0) {
				printf("Invalid\n");
			}
			else {
				int top = st.top();
				st.pop();
				block[top / 316]--;
				hashTable[top]--;
				cnt--;
				printf("%d\n", top);
			}
		}
		else {
			cnt++;
			scanf("%d", &e);
			st.push(e);
			block[e / 316]++;
			hashTable[e]++;
		}
	}
	return 0;
}
树状数组+二分法

树状数组的下标要从1开始

/*树状数组+二分*/
#include 
#include 
#include 
#include 

#define lowbit(i) (i&(-i))
using namespace std;
const int maxn = 100010;
int c[maxn];
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 k) {
	int l = 1, r = maxn, mid;
	while (l < r)
	{
		mid = (l + r) / 2;
		if (getSum(mid) < k)
		{
			l = mid + 1;
		}
		else {
			r = mid;
		}
	}
	printf("%d\n", r);
}

int main() {
	// 输入
	int n, cnt = 0, e;
	char op[12];
	stack<int> st;
	scanf("%d", &n);
	for (int i = 0; i < n; i++)
	{
		scanf("%s", op);
		if (!strcmp(op, "PeekMedian")) {

			if (cnt == 0) {
				printf("Invalid\n");
			}
			else
			{
				// 这里因为下标从1开始所以(cnt + 1)/2就是中位数的下标
				peekMedian((cnt + 1) >> 1); 
			}
		}
		else if (!strcmp(op, "Pop")) {
			if (cnt == 0) {
				printf("Invalid\n");
			}
			else {
				int top = st.top();
				st.pop();
				cnt--;
				printf("%d\n", top);
				update(top, -1);
			}
		}
		else {
			cnt++;
			scanf("%d", &e);
			st.push(e);
			update(e, 1);
		}
	}
	return 0;
}

你可能感兴趣的:(pat二刷)