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\) 开始,请找出下面式子的最大值:
其中 \(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;
}

浙公网安备 33010602011771号