T2 最长上升子序列(lis)题解

T2 最长上升子序列(lis)题解

题目大意

给定 \(1 \sim n\) 的排列 \(a\)。对每个 \(i = 1, 2, \dots, n-1\),交换 \(a_i\) 与 \(a_{i+1}\),求交换后的最长严格上升子序列(LIS)长度。各次交换相互独立,每次都从原排列开始。

思路

核心观察 :相邻交换后,LIS 长度只能是 \(L-1\)、\(L\) 或 \(L+1\)(\(L\) 为原 LIS 长度)。因为交换只影响同时包含这两个元素的子序列,删去其中一个就能恢复原排列的子序列。

分两种情况讨论:

情况一:\(a_i < a_{i+1}\)(递增对)

交换后不会产生新的上升子序列,只会破坏同时选择这两个元素的原子序列。

  • 如果两个位置都是必经位置 (每条 LIS 都必须经过它们),那么所有原 LIS 都被破坏,答案为 \(L-1\)
  • 否则,存在一条 LIS 不包含其中至少一个元素,这条 LIS 保留,答案仍为 \(L\)

情况二:\(a_i > a_{i+1}\)(递减对)

原 LIS 全部保留,答案不会减少。新增的上升子序列必须同时选择交换后的两个元素(\(y = a_{i+1}\) 在前,\(x = a_i\) 在后)。

设 \(f_i\) 为以 \(i\) 结尾的 LIS 长度,\(g_i\) 为以 \(i\) 开头的 LIS 长度,则:

\(ans_i = \max(L,\ f_{i+1} + g_i)\)

左侧选值 \(< y\) 的最长上升链(即 \(f_{i+1}\)),右侧选值 \(> x\) 的最长上升链(即 \(g_i\)),两部分可以直接拼接。

必经位置的判定

位置 \(i\) 属于某条 LIS 的充要条件:\(f_i + g_i - 1 = L\)。

按 \(f_i\) 分层,记第 \(k\) 层有多少个这样的候选。必经位置满足:

  • \(f_i + g_i - 1 = L\)(在某条 LIS 上)
  • 且 \(cntf_i = 1\)(这一层只有它一个候选,每条 LIS 都必须选它)

AC code

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

using ll = long long;

const int N = 200000 + 5;

int n;
int a[N], f[N], g[N];
int stk[N], top;
int cnt[N];

bool necessary[N];
int ans[N];

int main () {
	freopen("lis.in", "r", stdin);
	freopen("lis.out", "w", stdout);
	ios::sync_with_stdio (false);
	cin.tie (0);

	cin >> n;

	for (int i = 1; i <= n; i ++) cin >> a[i];

	top = 0;

	for (int i = 1; i <= n; i ++) {
		int k = lower_bound (stk + 1, stk + top + 1, a[i]) - stk;
		stk[k] = a[i];

		if (k > top) top = k;

		f[i] = k;
	}

	int L = top;

	top = 0;

	for (int i = n; i >= 1; i --) {
		int v = n + 1 - a[i];
		int k = lower_bound (stk + 1, stk + top + 1, v) - stk;
		stk[k] = v;

		if (k > top) top = k;

		g[i] = k;
	}

	for (int i = 1; i <= n; i ++)
		if (f[i] + g[i] - 1 == L) 
			cnt[f[i]] ++;

	for (int i = 1; i <= n; i ++)
		necessary[i] = (f[i] + g[i] - 1 == L && cnt[f[i]] == 1);

	for (int i = 1; i < n; i ++) {
		if (a[i] < a[i + 1]) ans[i] = L - (necessary[i] && necessary[i + 1]);
		else ans[i] = max (L, f[i + 1] + g[i]);
	}

	for (int i = 1; i < n; i ++) cout << ans[i] << ' ';

	return 0;
}
相关推荐
小此方2 天前
[算法实战论:动态规划]背包问题概念,01不满背包问题,01满背包问题,背包问题的滚动数组空间优化
算法·动态规划
冻柠檬飞冰走茶4 天前
字母移动游戏题解:贪心求最长可保留连续子串(附 C 实现)
c语言·开发语言·数据结构·算法·动态规划
Dream it possible!5 天前
LeetCode 面试经典 150_多维动态规划_交错字符串(97_C++_中等)
算法·leetcode·面试·动态规划
小此方5 天前
[算法实战论:动态规划]回文串问题的dp解法,回文串问题的母题,以及以此为基础的派生题
算法·动态规划
-梅6 天前
算法(1) 动态规划
算法·动态规划
UIU1146 天前
P1481 魔族密码
c++·算法·动态规划
j7~10 天前
【动态规划算法专题】动态规划五部曲实战:泰波那契数、三步问题、最小花费爬楼梯
c++·算法·动态规划·数据结构与算法
Tisfy10 天前
LeetCode 0453.最小操作次数使数组元素相等:两次遍历(反向思维)
算法·leetcode·题解·遍历·反向思维
孤独的Bochi10 天前
# [CSP-S 2026] 迁徙 / migrate 题解
题解
Tisfy10 天前
LeetCode 3665.统计镜子反射路径数目:三个动态规划数组
leetcode·动态规划·代理模式·题解