题目
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位置
以下不通:呜呜呜
前面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){
if(l==r){
tr[u]={l,r,a[r]};
return ;
}
tr[u]={l,r};
int mid=(l+r)>>1;
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;
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;
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);
}
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;
}
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){
if(l==r){
tr[u]={l,r,a[r]};
q[a[r]].push(r);
return ;
}
tr[u]={l,r};
int mid=(l+r)>>1;
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;
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;
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];
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;
}
else{
int pos=q[b[i]].front();
q[b[i]].pop();
int minn=query(1,1,pos);
if(minn!=b[i]){
f=true;
}
else{
modify(1,pos,inf);
}
}
if(f)break;
}
if(f)cout<<"NO";
else cout<<"YES";
if(t)cout<<endl;
}
return 0;
}
|