标签:lin 分段 一段 正整数 相同 const 变化 return 不同
传送门
补这道题的契机是因为烂桥杯2013的最后一题,虽然那道题暴力也能过,但看到大佬介绍的线段树做法,感觉又刷新了我对于线段树的认识,果然线段树是无所不能的。
给你一个 \(n\) 的全排列 \(A\),你可以从中选两个不重合的区间,如果这两个区间里的所有数按升序排列是一个公差为 \(1\) 的等差数列,则是一种合法方案,若两组不同区间包含的数相同,则视为一种方案。问一共有多少种合法方案。
考虑如果已知正整数区间 \([l,r]\) 在原全排列 \(A\) 中被分为 \(k\) 段,此时加入 \(r+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;
}
CodeForces 193D. Two Segments【线段树】
标签:lin 分段 一段 正整数 相同 const 变化 return 不同
原文地址:https://www.cnblogs.com/BakaCirno/p/12466819.html