【HDU6635】Nonsense Time

题目大意:给定一个长度为 N 的序列,开始时所有的位置都不可用,每经过一个时间单位,都会有一个位置被激活。现给定激活顺序的序列,求每次激活之后全局的最长上升子序列的长度,保证数据随机。

题解:
引理:数据随机的情况下,一个长度为 (N) 的序列的最长上升子序列的期望长度为 (O(sqrt n))。(暂时不会证明qaq)
发现若正序考虑,判断每次被激活位置的值是否对当前全局最优解有贡献这一操作所消耗的复杂度过高,无法接受。考虑时间倒流,即:初始时所有位置的值均被激活,每次会有一个位置的值无效。根据引理可知,(i) 个数字的 (LIS) 的期望值为 (O(sqrt i)),即:每 (sqrt n) 个数字无效时,全局 (LIS) 才会减少 1。因此,只需要判断每次无效的数是否在全局 (LIS) 中,若存在,则暴力重构全局最优解,否则直接输出答案即可。时间复杂度为 (O(nsqrt nlogn))

代码如下

#include <bits/stdc++.h>
using namespace std;

int main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	int T;
	cin >> T;
	while (T--) {
		int n;
		cin >> n;
		vector<int> a(n), melt(n);
		for (int i = 0; i < n; i++) {
			cin >> a[i];	
		}
		for (int i = 0; i < n; i++) {
			cin >> melt[i];
			melt[i]--;
		}
		vector<bool> frozen(n), book(n);
		vector<int> ans(n), dp(n), pos(n);
		auto RunLIS = [&]() {
			fill(dp.begin(), dp.end(), 1e9);
			fill(book.begin(), book.end(), 0);
			fill(pos.begin(), pos.end(), -1);
			int r = 0;
			for (int i = 0; i < n; i++) {
				if (frozen[i] == 0) {
					int id = lower_bound(dp.begin(), dp.end(), a[i]) - dp.begin();
					if (dp[id] == 1e9) {
						r = max(r, id);
					}
					dp[id] = a[i], pos[i] = id;
				}
			}
			for (int i = n - 1, now = 1e9, len = r; i >= 0; i--) {
				if (len < 0) break;
				if (pos[i] == len && a[i] < now) {
					now = a[i];
					len--;
					book[i] = 1;
				}
			}
			return r + 1;
		};
		int ret = RunLIS();
		for (int i = n - 1; i >= 0; i--) {
			ans[i] = ret;
			frozen[melt[i]] = 1;
			if (book[melt[i]] == 1) {
				ret = RunLIS();
			}
		}
		cout << ans[0];
		for (int i = 1; i < n; i++) {
			cout << " " << ans[i];
		}
		cout << endl;
	}
	return 0;
}
原文地址:https://www.cnblogs.com/wzj-xhjbk/p/11456581.html