Daimayuan Online Judge #613. 好序列(思维题 暴力/启发式分裂)

题目

思路来源

知乎严格鸽 (暴力/启发式分裂)代码源每日一题 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;
}

猜你喜欢

转载自blog.csdn.net/Code92007/article/details/130551007