D. Subarray Sorting9(思维+线段树)

本文详细分析了D.SubarraySorting问题的解法,重点在于理解如何利用线段树判断序列匹配的条件,避免不必要的排序操作。关键在于找到b[i]在已排序a数组中的正确位置,确保其后的小元素不会阻碍其匹配。作者通过实例和修正后的代码,揭示了正确使用线段树进行区间查询的重要性。
摘要由CSDN通过智能技术生成

题目

D. Subarray Sorting

分析

这道题不该盯着排序,盯着怎么排序,只需要思考序列需要满足
怎么样的条件才能使得 a,b数组通过排序能一一匹配成功
从左到右依次考虑a[i]==b[i]
首先要找到b[i]在a数组当中的位置,如果不存在肯定直接NO
b[i]在a数组当中出现的位置可能有多个,用队列记录下来,
找到b[i]在a种第一次出现的位置x,看b[i]?=min{1,x}
如果b[i]不是最小的,对这段进行排序必然会有更小的阻碍a[x]到i位置
//	(当然了,最小的相对的是未匹配的元素,匹配好了的放在b[i]前面的元素
//	不再参与比较,因为不会再移动了,所以更新匹配元素在线段树中的值,
//设置成正无穷,不会影响后面的最小数排在前面)

以下不通:呜呜呜 

前面i-1个元素已经匹配好了,下一次真正要排序的段落是{i,x} 
为什么要找{1,x}的最小值,而不是{i,x}的最小值呢
线段树查询l,r可以查询到任意区间段的呀
这是因为从左到右依次匹配,1~i-1必然是匹配好了的,且在匹配过程中
我们并没有维护a中元素的位置,只知道把小的元素调到前面,剩余
未匹配的元素必然是后移或是不动的 
而线段树中的{1,x}这些元素,a数组中已经排序好的前i-1个元素未必
全来自原线段树{1,x},也就是,原线段树中的{1,i}中可能有些元素可能
后移到了b[i]以后
即在{1,x}区间内,未匹配的元素都在b[i]后面了,但是一定会在x前面

woc,正难则反,不用寻根究底到底该对哪一段进行排序,之前的排序操作
是否会对后面的序列局势造成影响
总之,无论是匹配b[1]还是b[i],b[i]在a数组中第一次出现的位置为pos
那么a[1~pos]不能出现小于b[i]的尚未匹配的数,否则这个尚未匹配的数, 
在a数组中前于a[pos],且小于a[pos],不管之前进行了怎样的排序,该数
总会阻碍a[pos]归位(由于已经匹配的数不会对后面的数归位造成影响,
于是在线段树维护的a数组中将已经匹配的数设置为无穷大) 

线段树4个小小函数,找bug找到吐血

  • build分l,r ;递归参数有mid
  • modify,query分 t r [ u ] . l tr[u].l tr[u].l,递归参数永远是最初查找范围【l,r】
  • modify,query千万不要else if,因为修改/查询的段落可能只落在左子树,可能只落在右子树,可能二者皆有一部分
  • 函数中的递归参数没有 1 1 1,只有 l l l
#include <bits/stdc++.h>
#include <iostream> 
using namespace std;
const int N=3e5+5;
const int inf=0x3f3f3f3f;
struct node{
	int l,r,minx;
}tr[N*4]; 
int a[N];
int b[N];
queue<int> q[N];
void pushup(int u){//树上节点的编号 
	tr[u].minx=min(tr[u<<1].minx,tr[u<<1|1].minx);
}
void build(int u,int l,int r){//树上节点编号、w数组编号 
	if(l==r){//该段只有一个元素 
		tr[u]={l,r,a[r]};//树上节点维护者w数组中的元素下标噢l肯定等于r啦 
		return ; 
	}
	tr[u]={l,r};//这层递归下来l,r就是以u为根节点的树维护的w中下标范围 
	int mid=(l+r)>>1;//w数组元素分两半给左右子树
	build(u<<1,l,mid);
	build(u<<1|1,mid+1,r); 
	pushup(u); 
}

void modify(int u,int x,int d){
	if(tr[u].l==x&&tr[u].r==x){
		tr[u].minx=d;
		return;
	} 
	int mid=(tr[u].l+tr[u].r)>>1;//是在树中搜w下标x ,build是将w的下标分给左右子树 
	if(x<=mid)modify(u<<1,x,d);
	else modify(u<<1|1,x,d); 
	pushup(u);
}
int query(int u,int l,int r){
	if(tr[u].l>=l&&tr[u].r<=r){//整棵树都在查询范围内 
		return tr[u].minx;
	} 
	int mid=(tr[u].l+tr[u].r)>>1;//是在树中搜w下标x ,build是将w的下标分给左右子树 
	int res=0x3f3f3f3f; 
	if(l<=mid)res=query(u<<1,l,r);
	if(r>mid)res=min(res,query(u<<1|1,l,r)); 
	return res;
}
signed main(){//单点修改,区间最小值 
	ios::sync_with_stdio(false);
	cin.tie(0); 
	int t,n;
	cin>>t;
	while(t--){
		for(int i=1;i<=n;i++){
			while(!q[i].empty())q[i].pop();
		}
		cin>>n;
		for(int i=1;i<=n;i++){
			cin>>a[i];
			q[a[i]].push(i);//存储a[i]这个值在a[]中存储的下标 
		}
		for(int i=1;i<=n;i++){
			cin>>b[i];
		}
		build(1,1,n);
		int f=false;
		for(int i=1;i<=n;i++){
			if(q[b[i]].empty()){
				f=true;
				break;//b[i]在a[i]中不存在 
			}
			else{
				int pos=q[b[i]].front();
				q[b[i]].pop();
				if(query(1,1,pos)!=b[i]){
					f=true;
					break;
				}
				else{
					modify(1,pos,inf);
				}
				
			} 

	}
		if(f)cout<<"NO";
		else cout<<"YES";
		if(t)cout<<endl;
	} 
	

	return 0;
}

1、线段树维护a[]的同时,将a[i]的下标插入队列
2、多次样例输入,每次清空队列通过q【a【i】】.pop()
其实a[i]范围和n的范围是一样的,q【i】.pop()这样清空也一样,但还是前者意义上更标准

#include <bits/stdc++.h>
#include <iostream> 
using namespace std;
const int N=3e5+5;
const int inf=0x3f3f3f3f;
struct node{
	int l,r,minx;
}tr[N*4]; 
int a[N];
int b[N];
queue<int> q[N];
void pushup(int u){//树上节点的编号 
	tr[u].minx=min(tr[u<<1].minx,tr[u<<1|1].minx);
}
void build(int u,int l,int r){//树上节点编号、w数组编号 
	if(l==r){//该段只有一个元素 
		tr[u]={l,r,a[r]};//树上节点维护者w数组中的元素下标噢l肯定等于r啦 
		q[a[r]].push(r);
		return ; 
	}
	tr[u]={l,r};//这层递归下来l,r就是以u为根节点的树维护的w中下标范围 
	int mid=(l+r)>>1;//w数组元素分两半给左右子树
	build(u<<1,l,mid);
	build(u<<1|1,mid+1,r); 
	pushup(u); 
}

void modify(int u,int x,int d){
	if(tr[u].l==tr[u].r){
		tr[u].minx=d;
		return;
	} 
	int mid=(tr[u].l+tr[u].r)>>1;//是在树中搜w下标x ,build是将w的下标分给左右子树 
	if(x<=mid)modify(u<<1,x,d);
	else modify(u<<1|1,x,d); 
	pushup(u);
}
int query(int u,int l,int r){
	if(tr[u].l>=l&&tr[u].r<=r){//整棵树都在查询范围内 
		return tr[u].minx;
	} 
	int mid=(tr[u].l+tr[u].r)>>1;//是在树中搜w下标x ,build是将w的下标分给左右子树 
	int res=inf; 
	if(l<=mid)res=min(res,query(u<<1,l,r));
	if(r>mid)res=min(res,query(u<<1|1,l,r)); 
	return res;
}
signed main(){//单点修改,区间最小值 
	ios::sync_with_stdio(false);
	cin.tie(0); 
	int t,n;
	cin>>t;
	while(t--){
		cin>>n;

		for(int i=1;i<=n;i++){
			cin>>a[i];
//			q[a[i]].push(i);//存储a[i]这个值在a[]中存储的下标 
while(!q[a[i]].empty())q[a[i]].pop();
		}
		build(1,1,n);
		for(int i=1;i<=n;i++){
			cin>>b[i];
		}

		int f=false;
		for(int i=1;i<=n;i++){
			if(q[b[i]].empty()){
				f=true;
//				break;//b[i]在a[i]中不存在 
			}
			else{
				int pos=q[b[i]].front();
				q[b[i]].pop();
				int minn=query(1,1,pos);
				if(minn!=b[i]){
					f=true;
//					break;
				}
				else{
					modify(1,pos,inf);
				}
				
			} 
			if(f)break;
		}
		if(f)cout<<"NO";
		else cout<<"YES";
		if(t)cout<<endl;
	} 
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值