题目
思路来源
知乎严格鸽 (暴力/启发式分裂)代码源每日一题 Div1 好序列 - 知乎
题解
启发式分裂,可以认为是启发式合并的逆过程
比较直白的想法是找到第一个只出现一次的数的位置x,然后分治[1,x-1]和[x+1,n]
但是这样最坏复杂度是O(n^2)的,于是可能就需要配合一些数据结构
比如,可以线段树i处维护和a[i]值相同的下一个位置,不存在置INF,
然后配合线段树二分,判断一个区间的最大值是否为INF,
删掉一个值时,更新线段树也二分更新,均摊下来每个位置只会被更新一次,但是这样很麻烦
启发式分裂,是启发式合并的逆过程,
考虑合并的过程,是两边的小区间被挂到中间的大区间上
那么,逆过程,
1. 如果遍历的区间较长,则区间近似占区间总长度一半,类似归并;
2. 如果遍历的区间长度较短,则通过线性次有效减少了区间长度,类似线性遍历
分治复杂度O(n^2),主要是无效遍历较多,
比如遍历了区间前一半及以上,但想要的唯一值在后一半
而通过两头同时遍历,就可避免这一点
代码
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+10;
int pre[N],nxt[N],a[N],n;
bool split(int L,int R) {
if (L >= R)return 1;
int x = L, y = R;
while (x <= y) {
if (pre[x] < L&&R < nxt[x])return split(L, x - 1) && split(x + 1, R);
if (pre[y] < L&&R < nxt[y])return split(L, y - 1) && split(y + 1, R);
x++, y--;
}
return 0;
}
void solve() {
map<int,int>mp;
cin >> n;
for (int i = 1; i <= n; i++)pre[i] = -1, nxt[i] = n + 1;
for (int i = 1; i <= n; i++)cin >> a[i];
for (int i = 1; i <= n; i++) {
pre[i] = mp[a[i]];
nxt[mp[a[i]]] = i;
mp[a[i]] = i;
}
if (split(1, n))cout << "non-boring" << endl;
else cout << "boring" << endl;
}
int main(){
int t;
cin >> t;
while(t--){
solve();
}
return 0;
}