一、题意
如果一个序列的任意连续子序列中至少有一个只出现一次的元素,则称这个序列是non-boring的。
输入一个长度为n的正整数序列(n<=200000),判断它是否是boring的。
二、解析
首先一起来学学语文:“任意连续子序列中至少有一个只出现一次的元素”.
这句话的反命题为:存在一个连续子序列,这个子序列中不存在只出现一次的元素。
因此我们首先在这个序列中找一个只出现一次的元素,若找不到,则序列是boring的。
若找到了,假设这个元素为a[p],则包含这个元素的所有子序列都满足non-boring的条件了,剩下的子序列就都是a[0...p-1]的子序列或a[p+1,...n-1]的子序列了。
相信说到这大家都能发现,这里就出现了子问题了,因此这是我们用分治法就好了,当两个子问题a[0...p-1]和a[p+1,...n-1]都是non-boring的,原序列才是non-boring的。
这里我们需要用O(1)时间来判断一个元素在一个区间中是否是唯一的。要实现这个,我们首先得先进行预处理,得到原序列的l_same[i]数组和r_same[i]数组,他们分别表示以i为下标的元素左边和它位置最接近的相等的元素的下标,以及 右边和它位置最接近的相等的元素的下标。这个在输入时一并获取即可(具体见代码)。然后在后面我们在判断a[i]是否在a[l...r]是唯一的时候,就只需要判断l_same[i]和r_same[i]是否在[l...r]内即可。判断用时为O(1)。
还没完,我们分析一下时间复杂度,最坏情况下a[p]的位置在边缘,则时间复杂度满足T(n)=T(n-1)+O(n),即为O(n^2)。这样是会超时的。原因就是我们无法确保a[p]的位置尽量在中间。
面对这个问题,紫薯给了我们提示:使用中途相遇法。
我们不要从左往右遍历寻找a[p],而是从两边同时向中间遍历来寻找,这时如果a[p]在边缘,则时间复杂度满足T(n)=T(n-1)+O(1),即为O(n);若a[p]在中间,则时间复杂度满足T(n)=2T(n/2)+O(n),即为O(nlgn)。这样就不会超时了。
三、代码
#include <iostream> #include <unordered_map> using namespace std; const int maxn = 200000 + 5; int kase, n, a[maxn], l_same[maxn], r_same[maxn]; unordered_map<int, int> pos; // bool solve(int l, int r) { // 该做法超时 // if(l >= r) return 1; // int i = l; // for(; i < r; i ++) if(l_same[i] < l && r_same[i] > r) break; // if(i == r) return 0; // return solve(l, i - 1) && solve(i + 1, r); // } bool solve(int l, int r) { // 通过中途相遇法避免超时 if(l >= r) return 1; int i = l, j = r, p = -1; while(i <= j && p == -1) { if(l_same[i] < l && r_same[i] > r) p = i; else if(l_same[j] < l && r_same[j] > r) p = j; i ++, j --; } if(p == -1) return 0; return solve(l, p - 1) && solve(p + 1, r); } int main() { cin >> kase; while(kase --) { cin >> n; pos.clear(); fill(l_same, l_same + n + 1, -1); fill(r_same, r_same + n + 1, n); for(int i = 0; i < n; i ++) { cin >> a[i]; if(pos.count(a[i])) { int prev_idx = pos[a[i]]; l_same[i] = prev_idx, r_same[prev_idx] = i; } pos[a[i]] = i; } cout << (solve(0, n - 1) ? "non-boring" : "boring") << endl; } }
四、归纳
- 分治法:分而治之,将大问题分解为若干小问题,然后分到最小时的问题答案是能直接得到的。这种算法十分常见啊。需要好好掌握。
- 中途相遇法:本题通过这个方法成功的降低了算法的最坏时间复杂度,我看了之后简直叹为观止。总之算是积累一下经验吧。
- O(1)时间判断一个元素是否在一个区间内是唯一的:通过预处理得到l_same[maxn]和r_same[maxn]数组。他们分别表示以i为下标的元素左边和它位置最接近的相等的元素的下标,以及 右边和它位置最接近的相等的元素的下标。这个在输入时一并获取即可(具体见代码)。然后在后面我们在判断a[i]是否在a[l...r]是唯一的时候,就只需要判断l_same[i]和r_same[i]是否在[l...r]内即可。