首页 > 技术文章 > 题解[51nod1555] 布丁怪

zsq259 2019-11-13 14:12 原文

题解[51nod1555] 布丁怪

题面

解析

本文参考这位dalao的题解

首先有一个巧妙的转换,

开一个数组记录每个横坐标的纵坐标,

简单来说就是对于点(x,y),令a[x]=y.

于是问题就变成了求满足区间最大值与最小值的差恰好等于区间长度的区间数.

于是可以考虑分治不要问我怎么想到的

设当前区间为l,r,中点为mid.

mx[i]=i~mid的最大值(l<=mid),mid+1到i的最大值(i>mid)

mn[i]同理.

分情况讨论:

1.区间最大值和最小值都在左边.

设右端点为j,这时候j要满足mn[j+1]<mn[i]或mx[j+1]>mx[i],否则j+1也在mn~mx这个范围里面,j肯定不能是端点.

然后再看j-i是否等于mx[i]-mn[i].

2.最大值在左边,最小值在右边.

因为mx和mn都是单调的,所以拿两个指针L和R维护满足要求的右端点,

然后设右端点为j,则mx[i]-mn[j]=j-i,

即mx[i]+i=mn[j]+j,

开个桶维护一下就行了.

code:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define ll long long
#define fre(x) freopen(x".in","r",stdin),freopen(x".out","w",stdout)
using namespace std;

inline int read(){
	int sum=0,f=1;char ch=getchar();
	while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){sum=sum*10+ch-'0';ch=getchar();}
	return f*sum;
}

const int N=1000005;
int n,a[N];ll ans,cnt[N];
int mx[N],mn[N];

inline void work(int l,int r,int m){
	mx[m]=mn[m]=a[m];
	for(int i=m-1;i>=l;i--){
		mx[i]=max(mx[i+1],a[i]);
		mn[i]=min(mn[i+1],a[i]);
	}
	mx[m+1]=mn[m+1]=a[m+1];
	for(int i=m+2;i<=r;i++){
		mx[i]=max(mx[i-1],a[i]);
		mn[i]=min(mn[i-1],a[i]);
	}
	int L=m+1,R=m,j=m;
	for(int i=m;i>=l;i--){
		while(j<r&&mx[j+1]<mx[i]&&mn[j+1]>mn[i]) j++;
		if(mx[i]-mn[i]==j-i&&j>m) ans++;
		while(R<r&&mx[R+1]<mx[i]) R++,cnt[R+mn[R]]++;
		while(L<=r&&mn[L]>mn[i]) cnt[L+mn[L]]--,L++;
		if(L<=R) ans+=cnt[i+mx[i]];
	}
	while(L<=r) cnt[L+mn[L]]--,L++;
	while(R<r) R++,cnt[R+mn[R]]++;
}

inline void solve(int l,int r){
	if(l==r){ans++;return ;}
	int mid=(l+r)>>1;
	solve(l,mid);solve(mid+1,r);
	work(l,r,mid);
	reverse(a+l,a+r+1);
	if((r-l+1)%2) mid--;
	work(l,r,mid);
	reverse(a+l,a+r+1);
}

signed main(){
	n=read();
	for(int i=1;i<=n;i++)
	{int x=read();a[x]=read();}
	solve(1,n);	
	printf("%lld\n",ans);
	return 0;
}

推荐阅读