排列的连续区间问题

问题:对于 \(1~n\) 的排列 \(a\) ,判定某一区间 \(l~r\) 的值是否连续。

转化:一个区间值域连续,等价于,这个区间内的相邻两数的数对的个数恰好等于区间长度减一

证明其实很显然,把区间中的数排序,只有排序后两两相邻这个区间才连续。

进而可以从左向右扫,用线段树维护每一个位置到当前扫到的节点之间的区间的相邻两数的数对的个数。

为了能够统计答案,把变量 \(i\) 的影响抵消掉,即,最开始线段树赋初值为下边。这样当扫到 \(i\) 的时候线段树 \(1~i\) 中最大值一定不超过 \(i\)\(i\) 的个数和位置(就是区间最大值的个数和位置)就代表了连续区间的左端点。

P4747 [CERC2017] Intrinsic Interval

题目

Description

对于正整数 \(1,2,3 \cdots n\) 的一个排列 \(\pi\),若它的一个子串 \(\pi[a..b]\) 排序后是连续正整数,则称 \(\pi[a..b]\) 是一个“区间”。例如对排列 \(pi={3,1,7,5,6,4,2}\),子串 \(\pi[3..6]\) 是一个“区间”(因为它包含 \(4,5,6,7\)),\(\pi[1..3]\) 则不是。

一个子串的“本征区间”是包含该子串的最短区间。“包含”是指:若 \(\pi[x..y]\) 的本征区间是 \(\pi[a..b]\),则 \(a \le x \le y \le b\)

给定一个排列 \(\pi\) 及其 \(m\) 个子串,求每个子串的“本征区间”。

\(1 \le n \le 100000,1 \le m \le 100000\)

Solution

从左向右扫描,线段树维护“当前位置到当前扫到的节点之间的区间的相邻两数的数对的个数+当前位置下标”的最大值即最大值最靠右的位置。

使用优先队列维护待处理的区间的左端点(因为只有区间左端点更靠右的区间处理完了才有可能处理左端点靠左的区间)。

时间复杂度 \(O(nlog(m+n))\)

#include<bits/stdc++.h>
#define pb push_back
#define N 100005
#define mid ((l+r)>>1)
using namespace std;
int n,p[N],m,i,pos[N],mx[N<<2],mxp[N<<2],tag[N<<2];
struct node{int l,num;};
struct ans_node{int l,r;}ans[N];
vector<node>q[N];
inline int ls(int p){return p<<1;}
inline int rs(int p){return p<<1|1;}
priority_queue<pair<int,int>>qu;
inline void push_up(int p){
	if(mx[rs(p)]>=mx[ls(p)])mx[p]=mx[rs(p)],mxp[p]=mxp[rs(p)];
	else mx[p]=mx[ls(p)],mxp[p]=mxp[ls(p)];
}
inline void build(int p,int l,int r){
	if(l==r)return mx[p]=l,mxp[p]=l,void();
	build(ls(p),l,mid);
	build(rs(p),mid+1,r);
	push_up(p);
}
inline void f(int p,int k){
	mx[p]+=k;
	tag[p]+=k;
}
inline void push_down(int p){
	f(ls(p),tag[p]);
	f(rs(p),tag[p]);
	tag[p]=0;
}
inline void addsum(int p,int l,int r,int L,int R){
	if(L<=l&&r<=R)return f(p,1),void();
	push_down(p);
	if(L<=mid)addsum(ls(p),l,mid,L,R);
	if(R>mid)addsum(rs(p),mid+1,r,L,R);
	push_up(p);
}
inline node getsum(int p,int l,int r,int L,int R){
	if(L<=l&&r<=R)return {mxp[p],mx[p]};
	push_down(p);
	node res={0,0},tmp;
	if(L<=mid)tmp=getsum(ls(p),l,mid,L,R),res=tmp;
	if(R>mid)tmp=getsum(rs(p),mid+1,r,L,R),tmp.num>=res.num?res=tmp,0:0;
	return res;
}
inline void out(int p,int l,int r){
	if(l==r)return cout<<mx[p]<<" ",void();
	push_down(p);
	out(ls(p),l,mid);
	out(rs(p),mid+1,r);
}
int main(){
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	for(cin>>n,i=1;i<=n;i++)cin>>p[i],pos[p[i]]=i;
	cin>>m,pos[0]=pos[n+1]=n+1,build(1,1,n);
	for(int i=1,l,r;i<=m;i++)cin>>l>>r,q[r].pb({l,i});
	for(i=1;i<=n;i++){
		if(pos[p[i]-1]<=i)addsum(1,1,n,1,pos[p[i]-1]);
		if(pos[p[i]+1]<=i)addsum(1,1,n,1,pos[p[i]+1]);
		for(auto j:q[i])qu.push({j.l,j.num});
		while(qu.size()){
			node res=getsum(1,1,n,1,qu.top().first);
			if(res.num==i)ans[qu.top().second]={res.l,i},qu.pop();
			else break;
		}
	}
	for(int i=1;i<=m;i++)cout<<ans[i].l<<' '<<ans[i].r<<'\n';
}

P6795 [SNOI2020] 排列 的部分分(20pt)

Description

有一个 \(n\) 阶排列 \(p\),其前 \(k\)\(p_1,p_2,\cdots,p_k\) 已经确定了。

定义排列 \(p\) 中,\([l,r]\) 是一个值域连续段当且仅当:

\[\max(p_l, p_{l+1}, \dots, p_r) - \min(p_l, p_{l+1}, \dots, p_r) = r-l \]

\(p\) 中值域连续段个数即所有 \(1 \le l \le r \le n\) 中值域连续段的总数。

请你求出:所有可能的排列 \(p\) 中,值域连续段个数的最大值,以及任意一种方案。

\(k=n, 1 \le n \le 2\times10^5\)

Solution

同上,只不过变成了线段树维护最大值及个数。

每次查询,若最大值为 \(i\) 则将答案累加上最大值个数。

#include <bits/stdc++.h>
#define ll long long
#define N 200005
using namespace std;
int n,k,p[N];
namespace solve1{
	bool vis[15];
	int kkk[15];
	int ans=0;
	inline void work(){
		int res=0;
		for(int l=1;l<=n;l++){
			int mx=0,mn=n;
			for(int r=l;r<=n;r++){
				mx=max(mx,p[r]);
				mn=min(mn,p[r]);
				if(mx-mn==r-l)res++;
			}
		}
		if(res>ans){
			ans=res;
			for(int i=1;i<=n;i++)kkk[i]=p[i];
		}
	}
	inline void dfs(int x){
		if(x==n+1)return work(),void();
		for(int i=1;i<=n;i++)if(!vis[i])vis[i]=1,p[x]=i,dfs(x+1),vis[i]=0;
	}
	inline void solve(){
		for(int i=1;i<=k;i++)vis[p[i]]=1;
		dfs(k+1);
		cout<<ans<<'\n';
		for(int i=1;i<=n;i++)cout<<kkk[i]<<' ';
	}
}
namespace solve2{
	#define mid ((l+r)>>1)
	struct node{int l,num;};
	int pos[N],mx[N<<2],cnt[N<<2],tag[N<<2];
	ll ans;
	inline int ls(int p){return p<<1;}
	inline int rs(int p){return p<<1|1;}
	inline void push_up(int p){
		if(mx[rs(p)]==mx[ls(p)])mx[p]=mx[ls(p)],cnt[p]=cnt[ls(p)]+cnt[rs(p)];
		else if(mx[ls(p)]>mx[rs(p)])mx[p]=mx[ls(p)],cnt[p]=cnt[ls(p)];
		else mx[p]=mx[rs(p)],cnt[p]=cnt[rs(p)];
	}
	inline void build(int p,int l,int r){
		if(l==r)return mx[p]=l,cnt[p]=1,void();
		build(ls(p),l,mid);
		build(rs(p),mid+1,r);
		push_up(p);
	}
	inline void f(int p,int k){
		mx[p]+=k;
		tag[p]+=k;
	}
	inline void push_down(int p){
		f(ls(p),tag[p]);
		f(rs(p),tag[p]);
		tag[p]=0;
	}
	inline void addsum(int p,int l,int r,int L,int R){
		if(L<=l&&r<=R)return f(p,1),void();
		push_down(p);
		if(L<=mid)addsum(ls(p),l,mid,L,R);
		if(R>mid)addsum(rs(p),mid+1,r,L,R);
		push_up(p);
	}
	inline node getsum(int p,int l,int r,int L,int R){
		if(L<=l&&r<=R)return {cnt[p],mx[p]};
		push_down(p);
		node res={0,0},tmp;
		if(L<=mid)tmp=getsum(ls(p),l,mid,L,R),res=tmp;
		if(R>mid)tmp=getsum(rs(p),mid+1,r,L,R),tmp.num>res.num?res=tmp,0:tmp.num==res.num?res.l+=tmp.l,0:0;
		return res;
	}
	inline void solve(){
		for(int i=1;i<=n;i++)pos[p[i]]=i;
		pos[0]=pos[n+1]=n+1;
		build(1,1,n);
		for(int i=1;i<=n;i++){
			if(pos[p[i]-1]<=i)addsum(1,1,n,1,pos[p[i]-1]);
			if(pos[p[i]+1]<=i)addsum(1,1,n,1,pos[p[i]+1]);
			auto tmp=getsum(1,1,n,1,i);
			if(tmp.num==i)ans+=tmp.l;
		}
		
		cout<<ans<<'\n';
		for(int i=1;i<=n;i++)cout<<p[i]<<' ';
	}
}
int main(){
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	cin>>n>>k;
	for(int i=1;i<=k;i++)cin>>p[i];
	if(k==0){
		cout<<1ll*n*(n+1)/2<<'\n';
		for(int i=1;i<=n;i++)cout<<i<<' ';
		return 0;
	}
	if(n<=10)return solve1::solve(),0;
	if(k==n)return solve2::solve(),0;
}
posted @ 2025-06-03 10:39  linjingxiang  阅读(61)  评论(0)    收藏  举报