CodeForces 193D. Two Segments线段树
Posted bakacirno
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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)) 的算法,这里就不赘述了。因为这道题的数据范围是 (nle 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](1le ile x)) 被分的段数会和 ([i,r]) 一样,因为 (r+1) 是和 (x) 紧紧贴♂在一起的嘛;而 ([i,r+1](x+1le ile r+1)) 比 ([i,r]) 所分的段数多 (1),原因就是 ([i,r]) 中没有数和 (r+1) 相邻,那么 (r+1) 会单独构成一段。所以这种情况可以表示为:
[ egin{aligned} f[i,r+1] & = f[i,r] &, &&1le &ile x f[i,r+1] & = f[i,r]+1 &, &&x+1le &ile r+1 end{aligned} ]如果在 ([l,r]) 中有两个数 (x,y(x<y)) 与 (r+1) 在 (A) 中相邻,那么 ([i,r+1](1le ile x)) 被分的段数比 ([i,r]) 少 (1),因为本来 (x,y) 是裂开的,加入 (r+1) 后这两段合起来了;而 ([i,r+1](x+1le ile y)) 与 ([i,r]) 相等,因为只有 (y) 一个数和 (r+1) 贴;而 ([i,r+1](y+1le ile r+1)) 比 ([i,r]) 多 (1)。可表示为:
[ egin{aligned} f[i,r+1] &= f[i,r]-1 &, &&1le &ile x f[i,r+1] &= f[i,r] &, &&x+1le &ile y f[i,r+1] &= f[i,r]+1 &, &&y+1le &ile 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;
}
以上是关于CodeForces 193D. Two Segments线段树的主要内容,如果未能解决你的问题,请参考以下文章
CodeForces - 240D. Merging Two Decks 贪心+暴力
Codeforces1138-A(D题)Sushi for Two
codeforces 1560D. Make a Power of Two 模拟+暴力