###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)=r−l。
那么 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,r1−1)<max(l,Mid),即区间
[
M
i
d
+
1
,
r
1
−
1
]
[Mid+1,r1-1]
[Mid+1,r1−1]是不合法的情况。
对于每一个左端点 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)=r−l。
把原式移项得:
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)=r−l
移项得:
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);
}