Jzoj4605 排序

227 篇文章 3 订阅
153 篇文章 0 订阅

2017.10.21更新,下面是原本的咸鱼做法

我们发现询问只有一个,我们考虑二分这个最后答案

我们将序列中所有大于mid的值变成1,否则变成0,那么,排序就变成了将序列的01放在头或尾,可以用线段树区间修改做到

#include<stdio.h>
#include<algorithm>
#define mid (l+r>>1)
#define ls x<<1
#define rs x<<1|1
using namespace std;
int s[400040][2],t[400040],w[100010],n,m;
int G[100010][3],q;
void pushdown(int len,int x){
	if(t[x]){
		t[ls]=t[rs]=t[x];
		int d=t[x]-1;
		s[ls][d]=len-(len>>1);
		s[rs][d]=len>>1;
		s[ls][d^1]=s[rs][d^1]=0;
		t[x]=0;
	}
}
void build(int l,int r,int x,int k){
	if(l==r){ 
		s[x][0]=s[x][1]=t[x]=0;
		s[x][w[l]>=k]=1; return; 
	}
	build(l,mid,ls,k);
	build(mid+1,r,rs,k);
	s[x][0]=s[ls][0]+s[rs][0];
	s[x][1]=s[ls][1]+s[rs][1];
	t[x]=0;
}
int query(int l,int r,int x,int L,int R){
	if(L<=l && r<=R){ return s[x][0]; }
	int ans=0,m=mid; pushdown(r-l+1,x);
	if(L<=m) ans+=query(l,m,ls,L,R);
	if(m<R) ans+=query(m+1,r,rs,L,R);
	return ans;
}
void update(int l,int r,int x,int L,int R,int p){
	if(L>R) return;
	if(L<=l && r<=R){ 
		t[x]=p+1; s[x][p]=r-l+1;
		s[x][!p]=0; return;
	}
	int m=mid; pushdown(r-l+1,x);
	if(L<=m) update(l,m,ls,L,R,p);
	if(m<R) update(m+1,r,rs,L,R,p);
	s[x][0]=s[ls][0]+s[rs][0];
	s[x][1]=s[ls][1]+s[rs][1];
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;++i) scanf("%d",w+i);
	for(int i=0;i<m;++i) scanf("%d%d%d",G[i],G[i]+1,G[i]+2);
	scanf("%d",&q);
	int l=0,r=n;
	for(int _m;l<r;){
		_m=l+r+1>>1;
		build(1,n,1,_m);
		for(int j=0;j<m;++j){
			int L=G[j][1],R=G[j][2];
			int v=query(1,n,1,L,R);
			if(!G[j][0]){ 
				update(1,n,1,L,L+v-1,0);
				update(1,n,1,L+v,R,1);
			} else {
				v=R-L+1-v;
				update(1,n,1,L,L+v-1,1);
				update(1,n,1,L+v,R,0);
			}
		}
		if(query(1,n,1,q,q)) r=_m-1; else l=_m;
	}
	printf("%d\n",l);
}

-----------------------------------------------分割线-----------------------------------------------------

然而这道题正解并不是如此(虽然网上大部分都是这么写

我们考虑,对于一个区间,我们可以用一种可以快速支持分裂和合并的数据结构来维护

没错,我们可以用splay,但是复杂度是nlg^2n,比上面的咸鱼做法还慢

所以我们考虑另一种做法

注意到题目给出的是一个排列而不是序列

所以我们可以用权值线段树来搞定

线段树,均摊lg n合并,合并的方法和可并堆基本就是一样的(可以参考我置顶的文章),不过每次都将两个儿子都合并而已

分裂?这个和可持久化treap一样啊,按照size分开就行了

最后,每颗线段树维护一个reverse标记表示是顺序还是倒序,总复杂度n Lg n

线段树合并这个东西还是很浪费空间的

#include<stdio.h>
#include<string.h>
#include<algorithm>
#define N 100010
#define mid (l+r>>1)
using namespace std;
struct Tree{ int l,r,s; } s[N<<6];
int n,m,cnt=0,rt[N],rev[N];
struct Set{
	int s[N<<2],M;
	void init(){
		for(M=1;M<=n;M<<=1); M--;
		for(int i=M+1;i<=n+M;++i) s[i]=1;
		for(int i=M;i;--i) s[i]=s[i<<1]+s[i<<1|1];
	}
	inline void remove(int x){ for(x+=M;x;x>>=1) --s[x]; }
	inline void insert(int x){ for(x+=M;x;x>>=1) ++s[x]; }
	inline int gPre(int x){
		if(s[x+M]) return x;
		for(x+=M;x;x>>=1)
			if((x&1)&&(s[x^1])){
				for(--x;x<=M;x=(s[x<<1|1]?x<<1|1:x<<1));
				return x-M;
			}
		return -1;
	}inline int gSuc(int x){
		if(s[x+M]) return x;
		for(x+=M;x;x>>=1)
			if((~x&1)&&(s[x^1])){
				for(++x;x<=M;x=(s[x<<1]?x<<1:x<<1|1));
				return x-M;
			}
		return -1;
	}
} c;
inline void ps(Tree& x){ x.s=s[x.l].s+s[x.r].s; }
void insert(int l,int r,int& x,int v){
	if(!x) x=++cnt; ++s[x].s;
	if(l==r) return;
	if(v<=mid) insert(l,mid,s[x].l,v);
		else insert(mid+1,r,s[x].r,v);
}
void merge(int& x,int y){
	if(!x || !y){ x^=y; return; }
	merge(s[x].l,s[y].l);
	merge(s[x].r,s[y].r);
	ps(s[x]);
}
void split(int& x,int& y,int l,int r,int k){
	if(k==0){ y=x; x=0; return; } else y=++cnt;
	if(s[s[x].l].s>k){
		s[y].r=s[x].r; s[x].r=0;
		split(s[x].l,s[y].l,l,mid,k);
	} else split(s[x].r,s[y].r,mid+1,r,k-s[s[x].l].s);
	ps(s[x]); ps(s[y]);
}
void cut(int p){
	int r1=c.gPre(p),r2;
	if(r1==p) return; 
	rev[p]=rev[r1]; r2=c.gSuc(p);
	if(!rev[p]) split(rt[r1],rt[p],1,n,p-r1);
	else split(rt[r1],rt[p],1,n,r2-p),swap(rt[r1],rt[p]);
	c.insert(p);
}
void join(int l,int r,int o){
	int r1=c.gSuc(l+1),r2=c.gPre(r);
	for(;r1<=r2;r1=c.gSuc(r1)){
		merge(rt[l],rt[r1]);
		c.remove(r1);
	}
	rev[l]=o; 
}
int gVal(int l,int r,int x){
	if(l==r) return l;
	if(s[s[x].l].s) return gVal(l,mid,s[x].l);
		else return gVal(mid+1,r,s[x].r);
}
int main(){
	scanf("%d%d",&n,&m);
	for(int x,i=1;i<=n;++i){
		scanf("%d",&x);
		insert(1,n,rt[i],x);
	}
	c.init();
	c.insert(n+1);
	for(int o,l,r;m--;){
		scanf("%d%d%d",&o,&l,&r);
		cut(l); cut(r+1); join(l,r,o);
	}
	scanf("%d",&m); cut(m); cut(m+1);
	printf("%d\n",gVal(1,n,rt[m]));
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值