CodeForces 193D. Two Segments【线段树】

传送门
补这道题的契机是因为烂桥杯2013的最后一题,虽然那道题暴力也能过,但看到大佬介绍的线段树做法,感觉又刷新了我对于线段树的认识,果然线段树是无所不能的

题意

给你一个 \(n\) 的全排列 \(A\),你可以从中选两个不重合的区间,如果这两个区间里的所有数按升序排列是一个公差为 \(1\) 的等差数列,则是一种合法方案,若两组不同区间包含的数相同,则视为一种方案。问一共有多少种合法方案。

题解

考虑如果已知正整数区间 \([l,r]\) 在原全排列 \(A\) 中被分为 \(k\) 段,此时加入 \(r+1\),有如下情况:

  • 如果 \(r+1\)\([l,r]\) 中任意一个数都不相邻,那么 \([l,r+1]\) 被分为 \(k+1\) 段。
  • 如果 \(r+1\)\([l,r]\) 中一个数相邻,那么 \([l,r+1]\) 依然被分为 \(k\) 段。
  • 如果 \(r+1\)\([l,r]\) 中两个数相邻,那么 \([l,r+1]\) 被分为 \(k-1\) 段。

既然这样可以得到一个 \(O(n^2)\) 的算法,这里就不赘述了。因为这道题的数据范围是 \(n\le 300\,000\)\(O(n^2)\) 的算法是接受不了的。
但是根据上述的分段变化规律,我们发现可以对区间进行更改,也就是说,以 \(f[l,r]\) 表示 \([l,r]\) 被拆分的段数,如果已知 \(f[1,r],f[2,r],...,f[r,r]\),这时对这些区间都加入 \(r+1\),有以下情况:

  • 如果在 \([1,r]\) 中没有数与 \(r+1\)\(A\) 中相邻,那么:

\[f[i,r+1]=f[i,r]+1 \]

  • 如果在 \([1,r]\) 有只一个数 \(x\)\(r+1\)\(A\) 中相邻,\([i,r+1](1\le i\le x)\) 被分的段数会和 \([i,r]\) 一样,因为 \(r+1\) 是和 \(x\) 紧紧贴♂在一起的嘛;而 \([i,r+1](x+1\le i\le r+1)\)\([i,r]\) 所分的段数多 \(1\),原因就是 \([i,r]\) 中没有数和 \(r+1\) 相邻,那么 \(r+1\) 会单独构成一段。所以这种情况可以表示为:

\[\begin{aligned} f[i,r+1] & = f[i,r] &, &&1\le &i\le x\\ f[i,r+1] & = f[i,r]+1 &, &&x+1\le &i\le r+1 \end{aligned} \]

  • 如果在 \([l,r]\) 中有两个数 \(x,y(x<y)\)\(r+1\)\(A\) 中相邻,那么 \([i,r+1](1\le i\le x)\) 被分的段数比 \([i,r]\)\(1\),因为本来 \(x,y\) 是裂开的,加入 \(r+1\) 后这两段合起来了;而 \([i,r+1](x+1\le i\le y)\)\([i,r]\) 相等,因为只有 \(y\) 一个数和 \(r+1\) 贴;而 \([i,r+1](y+1\le i\le r+1)\)\([i,r]\)\(1\)。可表示为:

\[\begin{aligned} f[i,r+1] &= f[i,r]-1 &, &&1\le &i\le x\\ f[i,r+1] &= f[i,r] &, &&x+1\le &i\le y\\ f[i,r+1] &= f[i,r]+1 &, &&y+1\le &i\le r+1 \end{aligned} \]

所以我们就可以用线段树来维护决定 \(r\)\(f[i,r]\) 的值,根据题意,每次加入 \(r+1\) 之后,只需要查询线段树中值为 \(1\)\(2\) 的叶节点的个数就行了。

简单说一下线段树的写法

因为根据 \(f[l,r]\) 的性质就可以知道线段树叶节点的最小值就是 \(1\),那么要查 \(1\)\(2\) 的个数,只需要维护区间最小值和最小值的个数和最小值 \(+1\) 的个数就行了。
注意 pushup 操作的写法,统计区间最小值,然后通过最小值查询左右子树中最小值的个数,尤其不要忘了统计全最小值 \(+1\) 的个数。

代码

#include <iostream>
#include <stdio.h>
using namespace std;
typedef long long LL;
const int N=3e5+10;
int n,a[N],p[N];

struct SegTree{
	#define mid (l+r>>1)
	int num1[N*4],num2[N*4],minv[N*4],tag[N*4];
	void pushdown(int id){
		minv[id<<1]+=tag[id];tag[id<<1]+=tag[id];
		minv[id<<1|1]+=tag[id];tag[id<<1|1]+=tag[id];
		tag[id]=0;
	}
	void pushup(int id){
		minv[id]=min(minv[id<<1],minv[id<<1|1]);
		num1[id]=num2[id]=0;
		if(minv[id<<1]==minv[id]) num1[id]+=num1[id<<1],num2[id]+=num2[id<<1];
		else if(minv[id<<1]==minv[id]+1) num2[id]+=num1[id<<1];
		if(minv[id<<1|1]==minv[id]) num1[id]+=num1[id<<1|1],num2[id]+=num2[id<<1|1];
		else if(minv[id<<1|1]==minv[id]+1) num2[id]+=num1[id<<1|1];
	}
	void build(int id,int l,int r){
		num1[id]=r-l+1;
		if(l==r) return;
		build(id<<1,l,mid);
		build(id<<1|1,mid+1,r);
	}
	void upd(int id,int l,int r,int L,int R,int x){
		if(L<=l&&r<=R) {minv[id]+=x;tag[id]+=x;return;}
		if(tag[id]) pushdown(id);
		if(L<=mid) upd(id<<1,l,mid,L,R,x);
		if(R>mid) upd(id<<1|1,mid+1,r,L,R,x);
		pushup(id);
	}
	int ask(int id,int l,int r,int L,int R){
		if(L<=l&&r<=R) {
			if(minv[id]==1) return num1[id]+num2[id];
			if(minv[id]==2) return num1[id];
			return 0;
		}
		if(tag[id]) pushdown(id);
		int res=0;
		if(L<=mid) res+=ask(id<<1,l,mid,L,R);
		if(R>mid) res+=ask(id<<1|1,mid+1,r,L,R);
		return res;
	}
	#undef mid
}tr;

int main(){
	scanf("%d",&n);
	for(int i=1,x;i<=n;i++) scanf("%d",&x),p[x]=i;
	LL ans=0;
	tr.build(1,1,n);
	for(int i=1;i<=n;i++){
		a[p[i]]=i;
		int x=a[p[i]-1],y=a[p[i]+1];
		if(x>y) swap(x,y);
		if(x) tr.upd(1,1,n,1,x,-1),tr.upd(1,1,n,y+1,i,1);
		else if(y) tr.upd(1,1,n,y+1,i,1);
		else tr.upd(1,1,n,1,i,1);
		ans+=tr.ask(1,1,n,1,i);
		//cout<<"->"<<ans<<endl;
	}
	ans-=n;
	cout<<ans<<endl;
	return 0;
}
posted @ 2020-03-12 01:35  BakaCirno  阅读(165)  评论(0编辑  收藏  举报