1. Building Palindromes
Problem:
有N个编号从1到N的block,每个由字母A到Z组成,对N个blocks进行Q次查询,每次查询给出block编号的左右边界Li和Ri,判断每次能否对该范围的blocks重新排序使其组成回文串。
Solution:
- 最简单直观的想法是对每次查询进行暴力判断,即为每次查询区间新建一个map存储不同字母出现的频次,如果区间长度为奇数,那么出现一次的字母恰好一个,如果区间长度为偶数,那么应该不存在仅出现一次的字母。实现代码如下,注意区间开闭。单次查询O(n),总时间复杂度O(N*Q)。
int n, q;
string s;
bool solve(int x, int y) {
int len = y - x;
if (len == 1)
return true;
if (len == 2)
return (s[x] == s[x + 1]);
map<char, int> m;
for (int j = x; j < y; ++j) {
m[s[j]]++;
}
map<char, int>::iterator it;
int cnt = 0;
for (it = m.begin(); it != m.end(); ++it) {
if ((it->second) & 1)
cnt++;
}
if (len % 2 == 0)
return (cnt == 0);
else
return (cnt == 1);
}
- 对上述代码进行优化,我们需要加快每个字母频次的计算,如果我们对整个字母串每个字母的出现频次计算前缀和,这样每次子串的各个字母出现频次查询只需要O(1)的时间。建立所有字母前缀和的时间为O(N * |character set|),每次区间查询用时O(|character set|),总的时间复杂度O((N+Q) * |character set|)。
对处理区间问题常用的前缀和、差分、树状数组、线段树相关知识的总结参考区间问题。
int n, q;
string s;
int a[100002][26];
void preprocess() {
for (int i = 0; i < n; ++i) {
for (int j = 0; j < 26; ++j) {
a[i + 1][j] = a[i][j];
}
a[i + 1][s[i] - 'A']++;
}
}
bool solve(int x, int y) {
int len = y - x;
int cnt = 0;
for (int i = 0; i < 26; ++i) {
if ((a[y][i] - a[x][i]) & 1)
cnt++;
}
if (len % 2 == 0) {
return (cnt == 0);
}
else {
return (cnt == 1);
}
}
int main() {
int T;
ios::sync_with_stdio(false);
cin >> T;
for (int t = 1; t <= T; ++t) {
cin >> n >> q;
cin >> s;
preprocess();
int res = 0;
for (int i = 0; i < q; ++i) {
int x, y;
cin >> x >> y;
if (solve(x - 1, y))
res++;
}
cout << "Case #" << t << ": " << res << endl;
}
}
根据回文的奇偶数规律上面根据len长度进行返回值的判断也可直接写作
if (cnt <= 1)
return true;
else
return false;
2. Diverse Subarray
Problem:
给定N件饰品,每件饰品的类型为Ai,现在需要选择某个连续区间内的饰品带走,当选择区间[l, r]内的饰品时如果某种类型的饰品的个数多于S个则该类饰品必须全部被丢掉。任意选择l和r,求能够带走的饰品的最大数量。
Solution:
- 最简单直观的想法仍然是对每个区间暴力判断,每个区间新建map记录每类饰品的个数,根据规则计算能带走的饰品的最大数目,更新结果。注意下面代码的优化,在第二轮循环中我们从后向前依次判断,当区间长度小于当前结果数时直接剪枝,对于T <= 100, N <= 1000的test set 1可过。
int n, s;
int a[100001];
int func(int x, int y) {
map<int, int> m;
for (int i = x; i <= y; i++) {
m[a[i]]++;
}
int res = y - x + 1;
map<int, int>::iterator it;
for (it = m.begin(); it != m.end(); it++) {
if ((it->second) > s) {
res -= (it->second);
}
}
return res;
}
int solve() {
int res = 1;
for (int i = 0; i < n - 1; i++) {
for (int j = n - 1; j > 0; j--) {
if((j - i + 1) < res)
break;
int tmp = func(i, j);
res = max(res, tmp);
}
}
return res;
}
- 设计更优的算法,我们首先固定区间起始点,试图在O(n)时间内计算出当前可能区间的最大可带饰品数,那么我们不能再使用map统计每个类型出现的次数,因为这样丢失了位置信息。我们可以将原始序列转换为基于类型判断的加减差分序列,这样问题转换为求每个固定起始点的转换序列的最大连续和,时间复杂度O(n^2)。
例如,
起始点为1 可构造
, 最大连续和为3
起始点为2 可构造
,最大连续和为4
如果在-2后仍出现相同类型,转换为+0即可,在上述起始点后移的过程中我们发现只需要将1对应的类型中的-2改为+1, 后续的第一个+0改为-2即可。这样我们对区间起始点遍历O(n)的时间也可以进行优化,只需使用满足两类操作的数据结构:
支持单点修改,支持区间求和
因此,我们可以使用树状数组或线段树来实现。
网友评论