【Codeforces 526F】【JZOJ4687】奇袭

###Description
经过侦查,你绘制出了魔族大本营的地图,然后发现,魔族大本营是一个N×N的网格图,一共有N支军队驻扎在一些网格中(不会有两只军队驻扎在一起)。
在大本营中,每有一个k×k(1≤k≤N)的子网格图包含恰好k支军队,我们袭击的难度就会增加1点。
现在请你根据绘制出的地图,告诉爱丽丝这次的袭击行动难度有多大。
输入保证每一行和每一列都恰有一只军队。

###Data Constraint
对于30%的数据,N ≤ 100
对于60%的数据,N ≤ 5000
对于100%的数据,N ≤ 50000

###Solution
对于30%的数据,暴力找即可。

那对于60+%的数据呢?

转化一下题目,我们把军队的x坐标看成下标,y坐标看成下标为x的权值(即 a x = y a_x=y ax=y),题目就变成了:问有多少个区间 [ L , R ] [L,R] [L,R]满足,它们中间的数是连续的一段。

进而转化为了:有多少个区间 [ L , R ] [L,R] [L,R]满足, m a x ( a L , ⋯   , a R ) − m i n ( a L , ⋯   , a R ) = r − l max(a_L,\cdots,a_R)-min(a_L,\cdots,a_R)=r-l max(aL,,aR)min(aL,,aR)=rl

那么 n 2 n^2 n2算法就出来了。

能不能更快一些呢?我们考虑分治。

对于一段区间 [ L , R ] [L,R] [L,R],它的答案等于区间 [ L , M i d ] [L,Mid] [L,Mid]的答案加上区间 [ M i d + 1 , R ] [Mid+1,R] [Mid+1,R]的答案再加上跨越中间的答案(这里 M i d Mid Mid表示区间 [ L , R ] [L,R] [L,R]的中点)。

我们先预处理出每个位置到 M i d Mid Mid的最小值,最大值。

(接下来的 m a x ( l , r ) max(l,r) max(l,r) m i n ( l , r ) min(l,r) min(l,r)表示区间 [ l , r ] [l,r] [l,r]的最大值,最小值)

对于跨越中间的答案,它的最值分布有四种情况:
####最大值和最小值都在左侧
这里写图片描述
我们先枚举一个 l l l,表示这个合法区间的左端点。如果最值都在左侧,那么根据题意,右端点 r = l + m a x ( l , M i d ) − m i n ( l , M i d ) r=l+max(l,Mid)-min(l,Mid) r=l+max(l,Mid)min(l,Mid)。右端点 r r r得出后,如何判断它的合法性?

如上图,首先 r ′ r' r端点肯定是不合法的(因为它没有跨越 M i d Mid Mid)。

然后我们看看 r r r端点。我们首先确定了最值都在左侧,那么这个 r r r端点必须满足:
m a x ( M i d + 1 , r ) < m a x ( l , M i d ) max(Mid+1,r)<max(l,Mid) max(Mid+1,r)<max(l,Mid)
m i n ( M i d + 1 , r ) > m i n ( l , M i d ) min(Mid+1,r)>min(l,Mid) min(Mid+1,r)>min(l,Mid)

####最大值最小值都在右侧
这种情况与以上的情况类似,只不过是枚举右端点,算出左端点,然后判断合法性。

####最大值在右侧,最小值在左侧
这里写图片描述
首先还是枚举左端点 l l l,我们知道, m a x ( M i d + 1 , i ) ( i > M i d ) max(Mid+1,i)(i>Mid) max(Mid+1,i)(i>Mid)随着 i i i的增大是单调不下降的, m i n ( M i d + 1 , i ) ( i > M i d ) min(Mid+1,i)(i>Mid) min(Mid+1,i)(i>Mid)也同理。

于是我们新建两个指针 r 1 r1 r1 r 2 r2 r2
r 2 r2 r2要使 m i n ( M i d + 1 , r 2 ) > m i n ( l , M i d ) min(Mid+1,r2)>min(l,Mid) min(Mid+1,r2)>min(l,Mid),这样才满足最小值在左侧的情况。
r 1 r1 r1要使 m a x ( M i d + 1 , r 1 − 1 ) < m a x ( l , M i d ) max(Mid+1,r1-1)<max(l,Mid) max(Mid+1,r11)<max(l,Mid),即区间 [ M i d + 1 , r 1 − 1 ] [Mid+1,r1-1] [Mid+1,r11]是不合法的情况。

对于每一个左端点 l l l,指针移动后, r 1 r1 r1 r 2 r2 r2之间的所有点都可以是合法的右端点。

那么怎么统计右端点个数呢?

我们知道对于一个合法的区间它满足 m a x ( l , r ) − m i n ( l , r ) = r − l max(l,r)-min(l,r)=r-l max(l,r)min(l,r)=rl
把原式移项得: m a x ( l , r ) − r = m i n ( l , r ) − l max(l,r)-r=min(l,r)-l max(l,r)r=min(l,r)l
对于这种情况就是: m a x ( M i d + 1 , r ) − r = m i n ( l , M i d ) − l max(Mid+1,r)-r=min(l,Mid)-l max(Mid+1,r)r=min(l,Mid)l

于是,对于移动的 r 2 r2 r2,我们把 m a x ( M i d + 1 , r 2 ) − r 2 max(Mid+1,r2)-r2 max(Mid+1,r2)r2丢进桶里,对于移动前的 r 1 r1 r1,我们再把 m a x ( M i d + 1 , r 1 ) − r 1 max(Mid+1,r1)-r1 max(Mid+1,r1)r1从桶里扔出来。

每次指针移动后,若 r 1 < = r 2 r1<=r2 r1<=r2则把答案加上 m i n ( l , M i d ) − l min(l,Mid)-l min(l,Mid)l所在桶里的个数。

注意每次桶都要清空。

####最大值在左侧,最小值在右侧
其实与以上方法是一样的,只是枚举的是右端点。

注意:区间满足条件与上稍有不同。
m a x ( l , M i d ) − m i n ( M i d + 1 , r ) = r − l max(l,Mid)-min(Mid+1,r)=r-l max(l,Mid)min(Mid+1,r)=rl
移项得: m a x ( l , M i d ) + l = m i n ( M i d + 1 , r ) + r max(l,Mid)+l=min(Mid+1,r)+r max(l,Mid)+l=min(Mid+1,r)+r

所以应该是将 m a x ( l 2 , M i d ) + l 2 max(l2,Mid)+l2 max(l2,Mid)+l2丢进桶里, m a x ( l 1 , M i d ) + l 1 max(l1,Mid)+l1 max(l1,Mid)+l1扔出来。


好,这样我们得到的实现复杂度是 O ( n l o g 2 n ) O(nlog_2n) O(nlog2n)的。

###Code

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#define fo(i,j,k) for(int i=j;i<=k;i++)
#define fd(i,j,k) for(int i=j;i>=k;i--)
#define N 300001
#define ll long long
#define P 600000
using namespace std;
int bz[P*2+1];
int a[N];
int maxl[N],maxr[N],minl[N],minr[N];
ll fz(int l,int r)
{
	if(l==r) return 1;
	int mid=(l+r)/2;
	maxl[mid]=minl[mid]=a[mid];
	fd(i,mid-1,l)
	{
		maxl[i]=max(maxl[i+1],a[i]);
		minl[i]=min(minl[i+1],a[i]);
	}
	maxr[mid+1]=minr[mid+1]=a[mid+1];
	ll t=0;
	fo(i,mid+2,r)
	{
		maxr[i]=max(maxr[i-1],a[i]);
		minr[i]=min(minr[i-1],a[i]);
	}
	//minmax|
	fd(i,mid,l)
	{
		int p=i+maxl[i]-minl[i];
		if(p<=mid || p>r) continue;
		if(minr[p]>minl[i] && maxr[p]<maxl[i]) t++;
	}
	//|minmax
	fo(i,mid+1,r)
	{
		int p=i-maxr[i]+minr[i];
		if(p>mid || p<l) continue;
		if(minl[p]>minr[i] && maxl[p]<maxr[i]) t++;
	}
	//min|max
	int z1=mid+1,z2=mid;
	fd(i,mid,l)
	{
		while(minr[z2+1]>minl[i] && z2<r)
		{
			z2++;
			bz[maxr[z2]-z2+P]++;
		}
		while(maxl[i]>maxr[z1])
		{
			bz[maxr[z1]-z1+P]--;
			z1++;
			if(z1>r) break;
		}
		if(z1>r) break;
		if(z1<=z2) t+=bz[minl[i]-i+P];
	}
	fd(i,mid,l) bz[minl[i]-i+P]=0;
	fo(i,mid+1,r) bz[maxr[i]-i+P]=0;
	//max|min
	z1=mid,z2=mid+1;
	fo(i,mid+1,r)
	{
		while(minl[z2-1]>minr[i] && z2>l)
		{
			z2--;
			bz[maxl[z2]+z2+P]++;
		}
		while(maxr[i]>maxl[z1])
		{
			bz[maxl[z1]+z1+P]--;
			z1--;
			if(z1<l) break;
		}
		if(z1<l) break;
		if(z2<=z1) t+=bz[minr[i]+i+P];
	}
	fo(i,mid+1,r) bz[minr[i]+i+P]=0;
	fd(i,mid,l) bz[maxl[i]+i+P]=0;
	return t+fz(l,mid)+fz(mid+1,r);
}
int main()
{
	int n;
	cin>>n;
	fo(i,1,n)
	{
		int x,y;
		scanf("%d %d",&x,&y);
		a[x]=y;
	}
	cout<<fz(1,n);
}
posted @ 2020-12-23 18:41  sadstone  阅读(58)  评论(0编辑  收藏  举报