51Nod - 1249 近似有序区间 (单调栈+树状数组+偏序)(好题)

题目链接
极大极小子段,就是一个序列,它的最小值在最前面,它的最大值在最后面。现在给你一个由1~n的一个排列构成的数组S,求它有多少个这样的子段

例如:S = {3, 1, 2, 5, 4},S的所有极大极小子段为: {3}, {1}, {1, 2}, {1, 2, 5}, {2}, {2, 5}, {5}, {4}

Input
第一行:一个数N,表示S的长度。(1 <= N <= 50000) 第2 - N + 1行:每行1个数,对应1 - N的排列。

Output
输出S的极大极小子段数量。

Sample Input
5
3
1
2
5
4
Sample Output
8
思路:这道题的关键在于区间满足什么样的条件才能有效,对于一个区间【i,j】来说,如果b【j】<=i&&d【i】>=j&&i>=j,其中b【i】代表第i个数字作为最大值的最左的端点,d【i】代表第i个数字作为最小值的最右的端点,如果能发现这个的话就成功了一半了,b数组和d数组的话可以用单调栈来求,剩下的工作就是标准的偏序问题了,我们先固定一段,也就是先把b数组从小到大排序,这样就可以保证在i之前的b数组都满足b【j】<=i了,然后用树状数组求【i,d【i】】的个数就行了,贡献累加。

#include
using namespace std;
typedef long long ll;
#define lowbit(i) (i)&(-i)
const int maxn=1e5+5;
int n,a[maxn],d[maxn],c[maxn],p[maxn];
pair<int,int>b[maxn];
stack<int>s; 
void update(int x,int v)
{
	while(x<maxn) c[x]+=v,x+=lowbit(x);
}
int query(int x)
{
	int res=0;
	while(x>0) res+=c[x],x-=lowbit(x);
	return res;
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;++i) scanf("%d",&a[i]);
	for(int i=1;i<=n;++i)
	{
		while(!s.empty()&&a[s.top()]<=a[i]) s.pop();
		b[i].first=1;
		if(!s.empty()) b[i].first=s.top()+1;
		b[i].second=i;
		s.push(i);
	}
	while(!s.empty()) s.pop();
	for(int i=n;i>=1;--i)
	{
		while(!s.empty()&&a[s.top()]>=a[i]) s.pop();
		d[i]=n;
		if(!s.empty()) d[i]=s.top()-1;
		s.push(i);
	}
	sort(b+1,b+1+n);
	int now=1,ans=0;
	for(int i=1;i<=n;++i)
	{
		while(now<=n&&b[now].first<=i) update(b[now].second,1),now++;
		ans+=query(d[i])-query(i-1);
	}
	printf("%d\n",ans);
}

你可能感兴趣的:(树状数组)