9.9 数据结构测试 题解

9.9 数据结构测试 题解

A. OIer 密码(栈)

题意

一个无连续字符的字符串(只含小写字母),进行若干次操作,每次操作在某一位置插入连续两个相同的小写字母。给定操作后的字符串,求原字符串。

给定字符串长度 \(n\le10^6\)

思路

经典的括号匹配问题,这里用 stl 栈解决(因为手写的不知道为什么挂了)。

对于字符串的每一位(记为 \(s_i\)),如果栈为空或者栈顶元素与 \(s_i\) 不同,则入栈;如果相同,则将栈顶元素弹出。最后栈中的字符从栈底到栈顶即为原字符串。

注意:最后将所有字符弹出栈并记录后要倒序输出(因为先入后出)。

代码

#include <iostream>
#include <stack>
using namespace std;
int const N = 1e6 + 10;
int n, top;
stack<char> st;
char ch, ans[N];

int main() {
    
	while (cin >> ch) {
		if (st.empty() || st.top() != ch) st.push(ch);
		else st.pop();
	}
	while (!st.empty()) {
		ans[++n] = st.top();
		st.pop();
	}
	for (int i = n; i >= 1; i--) cout << ans[i];
	cout << '\n';
    
	return 0;
}

B. 投资(单调队列变形)

题意

给定一个序列 \(a_i\),长度为 \(n\)。给定两个值 \(s\)\(e\),求出长度在 \(s\)\(e\) 之间(包括 \(s\)\(e\))的连续子段和的最大值。

\(1\le n\le10^5\)\(|a_i|\le10^4\)

思路

普通的单调队列模板中,一般是限制子段的最大长度,而这里是同时限制了最大长度和最小长度。

考虑将区间 \([i,j]\) 分为两段,即 \([i,j-s+1]\)\((j-s+1,j]\),那么第二段的长度为 \(s-1\),所以第一段的长度范围为 \([1,e-s+1]\)

于是可以像普通的单调队列一样维护 \([1,n-s+1]\) 范围内的长度不超过 \(e-s+1\) 的最大连续子段和,并且在计算时加上第二段的和。

代码

#include <iostream>
#include <climits>
#define f(x, y, z) for (int x = (y); (x) <= (z); (x)++)
using namespace std;
const int N = 1e5 + 10;
int n, s, e, a, ans = INT_MIN;
int h, t, q[N], w[N];

int main() {
	ios::sync_with_stdio(false);
	cin.tie(0), cout.tie(0);
	
	cin >> n >> s >> e;
	f(i, 1, n) {
		cin >> a;
		w[i] = w[i - 1] + a;
	}
	h = t = 1;
	int len = e - s + 1;
	f(i, 1, n - s + 1) {
		while (h <= t && q[t] - q[h] + 1 > len) ++h;
		ans = max(ans, w[i + s - 1] - w[q[h]]);
		while (h <= t && w[q[t]] > w[i]) --t;
		q[++t] = i;
	}
	cout << ans << '\n';
		
	return 0;
}

C. 学习计划(堆 / 优先队列)

题意

\(n\) 门课程,每门课程在时间 \(t_i\) 之后将不再开放,并且每门课程要想学会需要 \(c_i\) 的时间,最多能够学习多少门课程。

思路

将课程按照 \(t\) 从小到大排序,遍历所有课程,选择策略如下:

  • 如果「学了这门课后的总时间」小于等于「这门课结束的时间」,那么选当前这门课;
  • 否则,如果「放弃学之前已经学的一节课」而「学当前这门课」能使总时间变少,那么放弃那门课,选择当前这门课。(由于按 \(t\) 排序,所以一定合法)

对于第二种情况,为了快速找到之前已经学的花费时间最长的一门课,我们用 大根堆 维护所有所选的课花费的时间。

代码

#include <iostream>
#include <queue>
#include <algorithm>
#define f(x, y, z) for (int x = (y); (x) <= (z); (x)++)
using namespace std;
const int N = 1e5 + 10;
int n, ans, tim; //tim:当前花费的总时间
struct node {
	int t, c;
	bool operator<(node const &o) const { return t < o.t; }
} a[N];
priority_queue<int> q; //大根堆存每门课需要的时间

int main() {
	ios::sync_with_stdio(false);
	cin.tie(0), cout.tie(0);
	
	cin >> n;
	f(i, 1, n) cin >> a[i].t >> a[i].c;
	sort(a + 1, a + n + 1);
	f(i, 1, n) {
		if (a[i].c > a[i].t) continue;
		if (tim + a[i].c <= a[i].t) { //时间足够学会这门课程
			++ans;
			q.emplace(a[i].c);
			tim += a[i].c;
		} else
			if (q.top() > a[i].c) { //当前这门课程比q.top()更优(需要时间更少)
				tim -= q.top();
				q.pop();
				q.emplace(a[i].c);
				tim += a[i].c;
			}
	}
	cout << ans << '\n';
	
	return 0;
}

D. 区间异或和最大(01-Trie)

题意

给定一个整数序列 \(\{a_n\}\),下标从 \(1\) 开始,请找出下面式子的最大值:

\[(a_{l_1} \oplus a_{l_1+1} \oplus \dots \oplus a_{r_1}) + (a_{l_2} \oplus a_{l_2+1} \oplus \dots \oplus a_{r_2}) \]

其中 \(1 \le l_1 \le r_1 \le l_2 \le r_2 \le n\)\(1\le n\le4\times10^5\)

思路

对于每一个 \(1\le i\le n\),求出区间 \([1,i]\) 和区间 \([i,n]\) 内的最大连续异或和并相加,统计结果的最大值,即为答案。

设区间 \([1,i]\) 内的最大连续异或和为 \(pre_i\),区间 \([i,n]\) 内的最大连续异或和为 \(suf_i\),那么答案为 \(\max\limits_{1\le i\le n}\{pre_i+suf_i\}\)

那么该如何计算 \(pre\)\(suf\) 呢?

\(pre\) 为例,设前缀异或和数组为 \(sum\),那么可以写出递推式:\(pre_i=\max\{pre_{i-1},sum_i\oplus sum_j\}\),其中 \(sum_j\) 是区间 \([1,i)\) 中与 \(sum_i\) 异或结果最大的前缀和。

我们想到 01-Trie 可以实现在一些数中查找与某个数 \(x\) 异或结果最大的那个数,并可以返回这个结果。

所以,我们可以将 \([1,i)\) 内的所有 \(sum\) 存到 01-Trie 树中,即可快速求出 \(sum_i\oplus sum_j\)

计算 \(suf\) 时同理,只需倒着跑一遍即可。

代码

#include <iostream>
#include <cstring>
#define f(x, y, z) for (int x = (y); (x) <= (z); (x)++)
#define g(x, y, z) for (int x = (y); (x) >= (z); (x)--)
#define il inline
using namespace std;
const int N = 4e5 + 10;
int n, a[N];
int pre[N], ans, tmp;

struct Trie{
	int tr[N * 30][2], cnt;
	il void Insert(int x);
	il int Query(int x); //查询所有数与x异或结果中的最大值
	il void Clear();
} tr;

il void Trie::Insert(int x) {
	int u = 0;
	g(i, 30, 0) {
		int c = (x >> i) & 1;
		if (!tr[u][c]) tr[u][c] = ++cnt;
		u = tr[u][c];
	}
	return;
}

il int Trie::Query(int x) {
	int u = 0, res = 0;
	g(i, 30, 0) {
		int c = (x >> i) & 1;
		if (tr[u][c ^ 1]) u = tr[u][c ^ 1], res |= (1 << i);
		else u = tr[u][c];
	}
	return res;
}

il void Trie::Clear() {
	memset(tr, 0, sizeof tr);
	cnt = 0;
	return;
}

int main() {
	ios::sync_with_stdio(false);
	cin.tie(0), cout.tie(0);
	
	cin >> n;
	f(i, 1, n) {
		cin >> a[i];
		tmp ^= a[i];
		tr.Insert(tmp);
		pre[i] = max(pre[i - 1], tr.Query(tmp));
	}
	tmp = 0;
	tr.Clear();
	g(i, n, 1) {
		tmp ^= a[i];
		tr.Insert(tmp);
		ans = max(ans, tr.Query(tmp) + pre[i]);
	}
	cout << ans << '\n';
	
	return 0;
}
posted @ 2022-11-06 20:27  f2021ljh  阅读(7)  评论(0)    收藏  举报