当前位置: 首页 > article >正文

代码源NOIP DAY2 T1 LIS和LDS题解

题意

给你两个数组 a 1 , a 2 , . . . , a n a_1,a_2,...,a_n a1,a2,...,an b 1 , b 2 , . . . , b n b_1, b_2, ..., b_n b1,b2,...,bn
你需要构造一个 1 1 1 n n n 的排列 p p p,满足对于所有 i i i

  • 以第 i i i结尾 的最长上升子序列长度为 a i a_i ai
  • 以第 i i i开头 的最长下降子序列长度为 b i b_i bi

数据保证存在一组解。

1 ≤ n ≤ 2 × 1 0 5 1 \leq n \leq 2 \times 10^5 1n2×105

分析

首先考虑给你一个数组 p p p 怎样求解最长上升子序列:

有一种做法是 f i = max ⁡ j < i f j + 1 ( p j < p i ) f_{i} = \max\limits_{j < i} f_{j} + 1(p_j < p_i) fi=j<imaxfj+1(pj<pi)

但是这样做对于给定 f f f 数组(即 a a a 数组)时,无法确定一些位置之间的大小关系。

另一种做法是我们从前往后求出 f i f_i fi,假设当前 f i = x f_i = x fi=x,那么我们另外开一个数组 g x g_x gx 代表长度为 x x x 的最长上升子序列的末尾最小值。
那么此时 g x g_x gx 数组显然单调递增。因为如果出现了 g x + 1 < g x g_{x + 1} < g_{x} gx+1<gx,那么我们把 g x + 1 g_{x + 1} gx+1 所在的长度为 x + 1 x + 1 x+1 的最长上升子序列去掉最后一个数,会得到一个长度为 x x x 的上升子序列。而这个子序列的末尾元素一定比 g x g_x gx 要小。
那么每次求 f i f_i fi 就可以在 g g g 数组上二分最后一个小于 p i p_i pi 的位置,然后就把 g f i g_{f_i} gfi 更新成 p i p_i pi

能够这样更新的原因是 p i p_i pi 一定比原来的 g f i g_{f_i} gfi 更小。因为如果更大的话 f i f_i fi 可以接上前面一个长度为 f i f_i fi 的子序列,那么 f i f_i fi 的值应该等于 f i + 1 f_i + 1 fi+1,与条件矛盾。

那么如果我们知道了最终的 f f f 数组,可以按照上述过程得到一些数的大小关系。例如一个 f i = 2 f_i = 2 fi=2 的位置 i i i 上面的数应该 大于之前最靠后的 f i ′ = 1 f_{i'} = 1 fi=1 的位置 i ′ i' i 上的数,小于之前最靠后的 f i ′ ′ = 2 f_{i''} =2 fi′′=2 的位置 i ′ ′ i'' i′′ 上的数

那么我们可以用一条 有向边 来表示这样的大小关系。可以连一条 i ′ i' i 指向 i i i 的有向边和一条 i i i 指向 i ′ ′ i'' i′′ 的有向边。

并且这样的 大小关系限制 f i f_i fi 数组的形态的 充要条件

只要能够满足这样的大小限制,就一定能得到 f f f 数组。对于本题而言,根据 a i a_i ai 我们可以连出一些有向边。根据 b i b_i bi 也可以连出一些有向边。我们把图建出来跑一遍拓扑排序即可。

具体的连边方式,以下只说明 a i a_i ai b i b_i bi 只需要倒着扫描数组即可:

  1. 连一条 l s t a i − 1 lst_{a_i - 1} lstai1 指向 i i i 的有向边。连一条 i i i 指向 l s t a i lst_{a_i} lstai 的有向边。
  2. l s t a i lst_{a_i} lstai 更新为 i i i

拓扑排序每次拿出的队头位置 p p p,令 p p p 上的数为还没填的最小数即可。
时间复杂度 O ( n ) O(n) O(n)

CODE:

#include<bits/stdc++.h>
#define pb emplace_back
using namespace std;
const int N = 2e5 + 10;
int n, a[N], b[N];
int lst[N];
int in[N];
vector< int > E[N];
int ans[N], rk;
int main() {
	scanf("%d", &n);
	for(int i = 1; i <= n; i ++ ) scanf("%d", &a[i]);
	for(int i = 1; i <= n; i ++ ) scanf("%d", &b[i]);
	for(int i = 1; i <= n; i ++ ) {
		int o = lst[a[i] - 1];
		int p = lst[a[i]];
		if(o) {
			E[o].pb(i);
			in[i] ++;
		}
		if(p) {
			E[i].pb(p);
			in[p] ++;
		}
		lst[a[i]] = i;
	}
	memset(lst, 0, sizeof lst);
	for(int i = n; i >= 1; i -- ) {
		int o = lst[b[i] - 1];
		int p = lst[b[i]];
		if(o) {
			E[o].pb(i);
			in[i] ++;
		}
		if(p) {
			E[i].pb(p);
			in[p] ++;
		}
		lst[b[i]] = i;
	}
	queue< int > q;
	for(int i = 1; i <= n; i ++ ) {
		if(in[i] == 0) q.push(i);
	}
	while(!q.empty()) {
		int u = q.front(); q.pop();
		ans[u] = ++ rk;
		for(auto v : E[u]) {
			in[v] --;
			if(!in[v]) q.push(v);
		}
	}
	for(int i = 1; i <= n; i ++ ) printf("%d ", ans[i]);
	return 0;
}

http://www.kler.cn/a/377615.html

相关文章:

  • 你适合哪种tiktok广告账户类型?
  • 【设计模式】策略模式定义及其实现代码示例
  • windows查看net网络监听端口命令和工具(ipconfig、netstat、tasklist、TCPView)
  • 考研要求掌握的C语言程度(插入排序)
  • Spring框架的事务管理
  • 学习虚幻C++开发日志——定时器
  • Web Broker(Web服务应用程序)入门教程(5)
  • 2181、合并零之间的节点
  • PostgreSQL 删除重复数据
  • 【Eclipse系列】eclipse快捷键和设置
  • 群控系统服务端开发模式-应用开发-业务架构逻辑开发第一轮测试
  • 性能测试|linux服务器搭建JMeter+Grafana+Influxdb监控可视化平台
  • 测试华为GaussDB(DWS)数仓,并通过APISQL快速将(表、视图、存储过程)发布为API
  • [LeetCode] 面试题08.01 三步问题
  • clion远程配置docker ros2
  • 3D区块多重渐变围栏
  • 【Linux】mnt命名空间-操作
  • NLP segment-20-分词开源项目介绍 HanLP 未来十年的自然语言处理
  • SpringBoot 在初始化加载无法使用@Value的时候读取配置文件教程
  • Admin.NET源码学习(5:swagger使用浅析)
  • Flutter 简述(1)
  • vue常用的修饰符有哪些
  • 外观模式及运用场景
  • Apifox 10月更新|测试步骤支持添加脚本和数据库操作、测试场景支持回收站、变量支持「秘密」类型
  • 关于安卓Handler之延时我不准时
  • Nginx 报错400 Request Header Or Cookie Too Large