逆序数&线段树 cf_E67 Subarray Sorting

传送门

参考博客https://www.cnblogs.com/Dup4/p/11116198.html#d.-subarray-sorting

题意:

给两个数列a1,a2,……,an和b1,b2,……,bn。可以对a数列进行无限次区间排序,问是否能将a数列变成b数列。

思路:

记a数列中,a[i]前面比它大的元素的个数(类似于逆序数)为nx_a[i];b数列中,b[i]前面比它大的元素的个数为nx_b[i]。

要使a数列能够变成b数列,则对于任意i,nx_a[i] <= nx_b[i]。这两行瞎扯,与题解无关

可以用线段树来实现。

开一个nx数组,nx[t]记录在a排列中元素 t 的位置(是第几个)。如果有多个 t 则记录最前面那一个,并用 f 数组记下后一个的位置;如果a排列中没有元素t则nx[t] = INF,可以理解成把 t 放在了最后面。

而线段树则用来

  • 记录(更新)每个元素在a排列中的位置。
  • 查找区间内的元素在a排列中最靠前的位置。

最后,核心思路:

对于a[i]和a[j](a[i] > a[j]),如果nx[a[i]]  < nx[a[j]],那么a[i]和a[j]一定无法交换位置。

所以,对于b[i],所有比b[i]小的元素(1 ~ b[i]-1)在a排列中的位置(即query())都应该在b[i]在a排列中的位置(即nx[b[i]])后面。

从第一个开始遍历b排列,先判断是否上一行成立。如果可,将b[i]元素在a排列中的最靠前位置(nx[b[i]]),更新到下一个b[i]的位置;否则return false。

#include<bits/stdc++.h>
#define MAXN 300010
using namespace std;
static const int INF = 1e9; 
int n, a[MAXN], b[MAXN], cnt[MAXN], f[MAXN], nx[MAXN], node[MAXN << 2]; 
inline int lson(int node){
	return node << 1;
}
inline int rson(int node){
	return node << 1 | 1;
}
void build(int rt, int l, int r){
	node[rt] = INF;
	if(l == r) return;
	int mid = (l + r) >> 1; 
	build(lson(rt), l, mid);
	build(rson(rt), mid + 1, r);
}
void update(int rt, int l, int r, int pos, int x){
	if(l == r){ node[rt] = x; return;} 
	int mid = (l + r) >> 1;
	if(pos <= mid) update(lson(rt), l, mid, pos, x);
	else update(rson(rt), mid + 1, r, pos, x);
	node[rt] = min(node[lson(rt)], node[rson(rt)]);
}
int query(int rt, int l, int r, int L, int R){
	if(L > R) return INF;
	if(L <= l && r <= R) return node[rt];
	int mid = (l + r) >> 1, Min = INF;
	if(L <= mid) Min = min(Min, query(lson(rt), l, mid, L, R));
	if(mid < R) Min = min(Min, query(rson(rt), mid + 1, r, L, R));
	return Min;
} 
bool check(){
	//memset(cnt, 0, sizeof(cnt));  这样写会超时woc 
	for(int i = 1; i <= n; i++) cnt[i] = 0;
	for(int i = 1; i <= n; i++){ ++cnt[a[i]]; --cnt[b[i]];}
	for(int i = 1; i <= n; i++) if(cnt[i]) return false;
	return true;
}
void Init(){
	for(int i = 1; i <= n; i++) nx[i] = INF;
	for(int i = n; i >= 1; i--){ f[i] = nx[a[i]]; nx[a[i]] = i;}
}
bool solve(){
	if(!check()) return false;
	Init();
	build(1, 1, n);
	for(int i = 1; i <= n; i++) update(1, 1, n, i, nx[i]);
	for(int i = 1; i <= n; i++){//核心思路 
		if(query(1, 1, n, 1, b[i] - 1) < nx[b[i]]) return false;
		nx[b[i]] = f[nx[b[i]]];
		update(1, 1, n, b[i], nx[b[i]]);
	}
	return true;
}
int main(){
	int T;
	scanf("%d", &T);
	while(T--){
		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]);
		printf(solve() ? "YES\n" : "NO\n");
	}
	return 0;
} 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值