NowCoder-SummerDay4F: 23 Subsequences

题目 :23 Subsequences

Note

定义一个序列 $B=(b_1,b_2,\ldots,b_M)$ 是好的,当且仅当对于每一个 $i=2,3,\ldots,M$,都有
$$
2 \cdot b_{i-1} \le b_i \le 3 \cdot b_{i-1}
$$
特别的,长度为 $1$ 的序列总是好的。

给你一个长度为 $N$ 的正整数序列 $A = (a_1, a_2, \ldots, a_N)$。你需要回答 $Q$ 次询问,每次询问给出一个区间 $[l, r]$ ($1 \le l \le r \le n$),请你找出在子序列 $A[l\ldots r]$,最长的好的子序列的长度。

「约束」

  • $1 \le N, Q \le 2 \times 10^5$,$1 \le a_i \le 10^{18}$

这是一个经典的状态定义的技巧:

Tip

定义 $f_{i,j} =$ 以 $i$ 开头,长度为 $j$ 的好子序列中,最小的结束下标值

而这里为什么第二位可以是好子序列长度呢?

因为你发现每一位数至少是前一个的两倍,于是我们有 $log 10^{18}$ 大概是 $60$ 左右,所以第二维度大概是 $60$ 左右,这应该是没有问题的!

接下来考虑初始状态,那么显然是: $f_{i, 1} = i$

那么状态转移呢?

$f_{i,j} = min_{i \lt k, 2a_i \le a_k \le 3a_i} f_{k, j - 1}$

就是这样!

其实是考虑对于每一个序列,然后在前面添加新的满足条件的数字,这样当然可以维护最小值!

正是 In-place DP 这样,第一维偏序关系正好就可以使用反向循环维护,而第二维刚好可以用线段树来维护。

struct SegmentTree{
		ll size = 1;
		vector<ll> data;
		SegmentTree(ll n){
				while(size < n) size *= 2;
				data.assign(size * 2, LINF);
		}
		void update(ll at){
				while(at /= 2) data[at] = min(data[at * 2], data[at * 2 + 1]);
		}
		void set(ll at, ll val){
				at += size;
				if(data[at] <= val) return;
				data[at] = val;
				update(at);
		}
		ll get(ll l, ll r){
				ll ans = LINF;
				l += size; r += size;
				for(; l < r; l /= 2, r /= 2){
						if(l & 1) chmin(ans, data[l++]);
						if(r & 1) chmin(ans, data[--r]);
				}
				return ans;
		}
};

const int K = 60;

auto Mainsol = [](){
	INT(N, Q);
	VEC(ll, A, N);
	vec(ll, nums, N);
	rep(i, N) nums[i] = A[i];
	uniq(nums);
	int M = sz(nums);

	v<SegmentTree> seg; seg.reserve(K + 1);
	rep(len, K + 1) seg.pb(M);

	vector<a<int, K + 1>> f(N);
	rep(i, N) f[i].fill(N);

	rrep(i, N) {
		f[i][1] = i;

		ll lftv = 2 * A[i], rhtv = 3 * A[i];
		int lftpos = lower_bound(all(nums), lftv) - begin(nums);
		int rhtpos = upper_bound(all(nums), rhtv) - begin(nums);

		rep(len, 2, K + 1) if(lftpos < rhtpos) {
			ll res = seg[len - 1].get(lftpos, rhtpos);
			if(res < LINF) f[i][len] = res;
		}

		int vpos = lower_bound(all(nums), A[i]) - begin(nums);

		rep(len, 1, K + 1) if(f[i][len] < N) 
			seg[len].set(vpos, f[i][len]);
	}

	v<v<pii>> q(N);
	rep(i, Q) {
		INT(l, r); l --, r --;
		q[l].pb({r, i});
	}

	v<int> ans(Q), best(K + 1, N);

	rrep(l, N) {
		rep(len, 1, K + 1) chmin(best[len], f[l][len]);

		each(r, id, q[l]) {
			int res = 1;
			rep(len, 1, K + 1) if(best[len] <= r) res = len;
			ans[id] = res;
		}
	}

	each(c, ans) out(c);
};