[ARC125C]LIS to Original Sequence

137 篇文章 1 订阅
89 篇文章 0 订阅

题目

传送门 to AtCoder

思路

当我坐在马桶上的时候(没有冒犯 H a n d I n D e v i l \sf HandInDevil HandInDevil 的意思),我强忍睡意,对自己说:振作点,肯定有一个贪心策略能够指导生成一个解!

然后我就打了一个线段树做法

我的做法

作为一个普通的蒟蒻,我只会枚举 + + + 检查。怎么检查呢?也就是说,在确定了一些位置的时候,是否仍然有解呢?

考虑 O ( n log ⁡ n ) \mathcal O(n\log n) O(nlogn) d p \tt dp dp L I S LIS LIS 方法, g i = min ⁡ ( g_i=\min( gi=min( 所有长度为 i i i 的上升子序列的末尾元素 ) ) ),那么一个必要条件是:如果 a p a_p ap 还没有被放入序列,那么 g p > a p g_p>a_p gp>ap

必要性很显然,如果 g p < a p g_p<a_p gp<ap 那么放入 a p a_p ap 就会构造出长度为 p + 1 p+1 p+1 的上升子序列,一直到最后会得到长度为 k + 1 k+1 k+1 的上升子序列。

那么是否充分呢?考虑放入 a p a_p ap 之后,从大到小把 [ 1 , a p ) [1,a_p) [1,ap) 的没出现过的数字都放入序列。显然此时 a p − 1 a_{p-1} ap1 已被放入,所以 g p − 1 ⩽ a p − 1 < a p g_{p-1}\leqslant a_{p-1}<a_p gp1ap1<ap,于是会恰好令 g p = a p g_p=a_p gp=ap 。接下来从大到小放入元素,显然不会有人修改到 g p + 1 g_{p+1} gp+1 的值。而最初 g p + 1 > a p + 1 g_{p+1}>a_{p+1} gp+1>ap+1,所以接下来放入 a p + 1 a_{p+1} ap+1,并从大到小放入 [ 1 , a p + 1 ) [1,a_{p+1}) [1,ap+1) 未出现的数字,归纳法可知有解……吗?

注意到 a k ≠ n a_k\ne n ak=n 时,数字还没有放完!所以需要一开始就从大到小放入 ( a k , n ] (a_k,n] (ak,n] 的全部数字。于是又有了一个约束条件是 g k > n g_k>n gk>n(初值 + ∞ +\infty + 可以满足)。当然如果 n n n 已经被放入了呢?就应该是 n − 1 n-1 n1 。所以实际上是 g k > max ⁡ i ∉ a n s i g_k>\max_{i\notin ans}i gk>maxi/ansi

当然,这样是放入之后 c h e c k \rm check check,铁定超时。需要修改为 “预判”,即 g p − 1 < x < a p g_{p-1}<x<a_p gp1<x<ap x x x 不能填在下一位。用线段树维护限制条件,时间复杂度 O ( n log ⁡ n ) \mathcal O(n\log n) O(nlogn)

官方题解

上面的做法太蠢了!何等蠢物想出此种花拳绣腿,误人子弟!

不如想想为什么永远有解。当 k = 1 k=1 k=1 时,降序排列即可。当 k ⩾ 2 k\geqslant 2 k2 时,先构造出一个长度为 n − 1 n-1 n1 的排列,使得其 L I S LIS LIS ⟨ a 2 − 1 , a 3 − 1 , a 4 − 1 , … , a k − 1 ⟩ \langle a_2\mathrm{-}1,a_3{\rm-}1,a_4{\rm-}1,\dots,a_k{\rm-}1\rangle a21,a31,a41,,ak1,然后将这个排列中不小于 a 1 a_1 a1 的数都 + 1 +1 +1,最后在前面接上一个 a 1 a_1 a1

类似地,我们会发现,前两个数字可以 “随意” 指定。第一个数字肯定不能小于 a 1 a_1 a1,否则将会接在 a 1 a_1 a1 的前面;那么第一个数字最小就是 a 1 a_1 a1,为了让字典序最小。第二个数字可以是任意一个小于 a 1 a_1 a1 的数字,只需要同样构造长度为 n − 2 n-2 n2 的排列,使得 L I S = ⟨ a 2 − 2 , a 3 − 2 , a 4 − 2 , … , a k − 2 ⟩ LIS=\langle a_2\mathrm{-}2,a_3{\rm-}2,a_4{\rm-}2,\dots,a_k{\rm-}2\rangle LIS=a22,a32,a42,,ak2,然后将 a 1 a_1 a1 和这个数字 “插入” 进去。显然并不会得到一个长度为 k + 1 k+1 k+1 或更长的 L I S LIS LIS

那如果 a 1 = 1 a_1=1 a1=1 就确实没话说了,直接放在第一位,后面 n − 1 n-1 n1 位利用整体 + 1 +1 +1 来得到。

去掉递归,无非是这样的:第一位放 a 1 a_1 a1,第二位放 1 1 1,第三位放 a 2 a_2 a2,第四位放 2 2 2,以此类推。直到最后,把剩余数字排列成单减。如果 a x a_x ax 放完发现没有比自己小的数字,就不放。

时间复杂度 O ( n ) \mathcal O(n) O(n),而且好写。怪不得我在这个题上浪费了一个小时,校长却十五分钟切掉。

代码

线段树
#include <cstdio>
#include <iostream>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;
typedef long long int_;
# define rep(i,a,b) for(int i=(a); i<=(b); ++i)
# define drep(i,a,b) for(int i=(a); i>=(b); --i)
inline int readint(){
	int a = 0; char c = getchar(), f = 1;
	for(; c<'0'||c>'9'; c=getchar())
		if(c == '-') f = -f;
	for(; '0'<=c&&c<='9'; c=getchar())
		a = (a<<3)+(a<<1)+(c^48);
	return a*f;
}

const int MaxN = 200005;
int n;

namespace SgTree{
	int v[MaxN<<2], lazy[MaxN<<2];
	void pushUp(int o){
		v[o] = max(v[o<<1],v[o<<1|1]);
	}
	void addNode(int o,int x){
		v[o] += x, lazy[o] += x;
	}
	void pushDown(int o){
		addNode(o<<1,lazy[o]);
		addNode(o<<1|1,lazy[o]);
		lazy[o] = 0;
	}
	# define LSON o<<1,l,(l+r)>>1
	# define RSON o<<1|1,((l+r)>>1)+1,r
	void modify(int ql,int qr,int qv,int o=1,int l=1,int r=n){
		// if(o == 1) printf("MODIFY %d %d %d\n",ql,qr,qv);
		if(qr < l || r < ql) return ;
		if(ql <= l && r <= qr)
			return addNode(o,qv);
		pushDown(o); modify(ql,qr,qv,LSON);
		modify(ql,qr,qv,RSON); pushUp(o);
	}
	int query(int o=1,int l=1,int r=n){
		// if(v[o] != 0) return -1;
		if(l == r) return l;
		pushDown(o);
		if(v[o<<1] == v[o])
			return query(LSON);
		else return query(RSON);
	}
}

const int infty = (1<<30)-1;
int a[MaxN], g[MaxN];
int b[MaxN]; // answer
bool used[MaxN];
int main(){
	n = readint();
	int k = readint();
	rep(i,1,k) a[i] = readint();
	rep(i,1,n) g[i] = infty;
	int mx = n; // big threat
	if(k == 1) // g[k-1] = 0
		SgTree::modify(1,mx-1,-1);
	if(a[1] != 1) // g[0] and a[1]
		SgTree::modify(1,a[1]-1,-1);
	for(int i=1,p=1; i<=n; ++i){
		// printf("i = %d\n",i);
		b[i] = SgTree::query();
		SgTree::modify(b[i],b[i],-infty); // remove
		if(b[i] == a[p]){ // make it
			SgTree::modify(g[p-1]+1,a[p]-1,1); // no more tag
			++ p; // move on
		}
		int dp = lower_bound(g+1,g+n+1,b[i])-g;
		if(g[dp] != infty && dp != k && dp+1 >= p)
			SgTree::modify(g[dp]+1,a[dp+1]-1,1); // restore
		if(g[k-1] != infty && mx)
			SgTree::modify(g[k-1]+1,mx-1,1); // restore
		g[dp] = b[i], used[b[i]] = true;
		if(dp != k && dp+1 >= p)
			SgTree::modify(g[dp]+1,a[dp+1]-1,-1); // put tag
		for(; used[mx]; --mx);
		if(g[k-1] != infty && mx)
			SgTree::modify(g[k-1]+1,mx-1,-1); // put tag
	}
	rep(i,1,n) printf("%d ",b[i]);
	putchar('\n');
	return 0;
}
贪心构造
#include <cstdio>
#include <iostream>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;
typedef long long int_;
# define rep(i,a,b) for(int i=(a); i<=(b); ++i)
# define drep(i,a,b) for(int i=(a); i>=(b); --i)
inline int readint(){
	int a = 0; char c = getchar(), f = 1;
	for(; c<'0'||c>'9'; c=getchar())
		if(c == '-') f = -f;
	for(; '0'<=c&&c<='9'; c=getchar())
		a = (a<<3)+(a<<1)+(c^48);
	return a*f;
}
inline void writeint(int x){
	if(x > 9) writeint(x/10);
	putchar((x-x/10*10)^48);
}

const int MaxN = 200005;
bool used[MaxN];

int main(){
	int n = readint(), k = readint();
	for(int i=1,a,p=1; i<k; ++i){
		used[a = readint()] = true;
		writeint(a), putchar(' ');
		for(; used[p]; ++p);
		if(p < a){
			writeint(p), used[p] = 1;
			putchar(' ');
		}
	}
	for(int i=n; i; --i)
		if(!used[i]){
			writeint(i);
			putchar(' ');
		}
	putchar('\n');
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值