JZOJ 4687 奇袭【NOIP2016提高A组8.12】

来源:互联网 发布:伊朗 知乎 编辑:程序博客网 时间:2024/05/22 06:42

奇袭

题目大意

给出一张n×n地图,上面有n支军队(数据保证每一行和每一列都恰好只有一只军队),
如果一个k×k(1≤kN)的子网格图包含恰好k支军队,怎我们称此子网格为”doublez神奇网格”,问此地图中共有多少个”doublez神奇网格”?

数据范围

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

题解

30分

定义:第i支军队的坐标为(Xi,Yi)。
首先想到的肯定就是用二维前缀和来做,枚举右下角的位置,再枚举k的值,判断一下类及答案即可。
但如果这样做的话,时间复杂度就是O(N3),只能拿30分。

60分

我们优化一下,尝试用O(N2)的算法去拿60分。
按照Xi从小到大排序。
我们发现,如果在一段长度为len的区间里,这段区间里 Yi的最大值-Yi的最小值=len,那么就说明这段区间包含的军队是一个len×len的”doublez神奇网格”,这是一个十分显然的结论。(我们称这种符合条件的区间暂且称为”doublez区间”)

枚举区间右边界,从右往左枚举左边界的同时用O(1)的时间维护最大最小值统计答案,枚举右边界O(n),枚举左边界也是O(n),所以时间复杂度为O(N2)。

100分

至于100分的正解,我们需要从60分的优化而来。
我们使用分治算法。
假设我们需要求区间(L,R)的答案,则
ans(L,R)=ans(L,mid)+ans(mid+1,R)+穿过mid的”doublez区间”个数。(其中mid=(L+R)2

ans(L,mid),ans(mid+1,R)这两项我们分治下去递归求就可以了,关键是怎么求第三项穿过mid的”doublez区间”个数。

我们对这种 区间进行分类讨论。
<1>最大值、最小值在mid的同侧。
<2>最大值、最小值在mid的异侧。

在进行讨论之前,我们先定义四个数组:
mil[i]=min(y[i],y[i+1]……,y[m-1],y[m]) =min(y[i],mil[i+1])(L<=i<=m)
mal[i]=max(y[i],y[i+1]……,y[m-1],y[m]) =max(y[i],mal[i+1])(L<=i<=m)

mir[i]=min(y[m+1],y[m+2],……y[r-1],y[r]) =min(y[i],mir[i-1])(m < i<=R)
mar[i]=max(y[m+1],y[m+2],……y[r-1],y[r]) =max(y[i],mar[i-1])(m < i<=R)

我们先考虑第一种情况 最大值、最小值在mid的同侧,若他们都在mid左侧,我们枚举区间左边界i,设右边界为j,根据上面提及过的定义可得

Yi的最大值-Yi的最小值=len ,即mal[i]-mil[i]=j-i

我们发现这个式子只有j是未知的,我们可以求出它的值。
求出j之后,我们看一下j是否大于mid(是否穿过mid),以及新得到的区间(i,j)的最大值和最小值是否为mal[i]、mil[i],若成立,则该区间为”doublez区间”。

最大值和最小值在mid右侧的情况类似,我就不讲了。

第一种情况讨论完了,我们就讨论第二种:最大值、最小值在mid的异侧。
若一个”doublez区间”的最小值在左侧,最大值在右侧,根据定义,则满足

Yi的最大值-Yi的最小值=len
mar[j]-mil[i]=j-i(j为区间右边界,i为区间左边界)

移项,得

mar[j]-j=mil[i]-i(j为区间右边界,i为区间左边界)

我们开一个桶,Ti表示i这个值目前出现了多少次。
有了这个定义,就可以做了。

我们枚举i,从mid枚举到L,我们用两个指针Z1Z2,从mid+1开始右移动,Z1不断往右移动直到满足mar[Z1]>mal[i],Z2不断往右移动直到mal[Z2+1] < mil[i],在i逐渐变小的过程中,mil[i]在逐渐变小(不上升),mal[i]在逐渐上升(不下降),所以Z1Z2也在不断往右移(不会往左移)。按照上述方法移动完Z1Z2后,会有以下结论:

这里写图片描述

我们让Z2移动过的区间里的每一个mar[j1]-j1放进桶里面,给相应位置加1(Tj1=Tj1+1),
再让Z1移动过的区间里的每一个mar[j2]-j2给同理的对应位置减1(Tj2=Tj2-1),这样下来就只有夹在Z1Z2中的那一段值生效了(即只有夹在Z1Z2中的那一段可以做以i为左边界的区间的右边界),答案加上Tmil[i]i

doublez区间”的最小值在右侧,最大值在左侧的情况差不多,需要注意的是此时,
mal[j]-mir[i]=i-j(j为区间左边界,i为区间右边界)。

上面一堆复杂的操作搞完以后,就可以分成两段继续统计答案了。时间复杂度O(n log n)

Code(Pascal)

var    t:array[-100000..100000] of longint;    x,y,kkk,uuu,ppp:array[0..60000] of longint;    n,j,m,k,l,r,i,o,p,ans:longint;       function max(a,b:longint):longint;    begin        if a>b then exit(a)        else exit(b);    end;function min(a,b:longint):longint;    begin        if a<b then exit(a)        else exit(b);    end;procedure ef(l,r:longint);    var        i,j,m,tt,zx,zd,r1,r2:longint;    begin        if l=r then exit;        m:=(l+r) div 2;        zx:=maxlongint;        zd:=0;        for i:=m downto l do        begin            zx:=min(zx,y[i]);            zd:=max(zd,y[i]);            kkk[i]:=zd;            uuu[i]:=zx;        end;        zx:=maxlongint;        zd:=0;        for i:=m+1 to r do        begin            zx:=min(zx,y[i]);            zd:=max(zd,y[i]);            kkk[i]:=zd;            uuu[i]:=zx;        end;        for i:=l to m do        begin            tt:=kkk[i]-uuu[i]+i;            if tt<=r then            if (tt>m) and (kkk[i]>kkk[tt]) and (uuu[i]<uuu[tt]) then inc(ans);        end;        for i:=m+1 to r do        begin            tt:=i-(kkk[i]-uuu[i]);            if tt>=l then            if (tt<=m) and (kkk[i]>kkk[tt]) and (uuu[i]<uuu[tt]) then inc(ans);        end;        r2:=m;        r1:=m+1;        for i:=m+1 to r do        begin            zd:=kkk[i];            zx:=uuu[i];            while (zx<uuu[r2]) and (r2>=l) do            begin                dec(t[uuu[r2]-r2]);                dec(r2);            end;            if r2<l then break;            while (zd>kkk[r1-1]) and (r1>l) do            begin                dec(r1);                inc(t[uuu[r1]-r1]);            end;            if r1<=r2 then            ans:=ans+t[zd-i];        end;        for i:=m downto l do        t[uuu[i]-i]:=0;        r1:=m+1;        r2:=m;        for i:=m downto l do        begin            zd:=kkk[i];            zx:=uuu[i];            while (zx<uuu[r1]) and (r1<=r) do            begin                dec(t[uuu[r1]+r1]);                inc(r1);            end;            if r1>r then break;            while (zd>kkk[r2+1]) and (r2<r) do            begin                inc(r2);                inc(t[uuu[r2]+r2]);            end;            if r2>=r1 then            ans:=ans+t[zd+i];        end;        for i:=m+1 to r do        t[uuu[i]+i]:=0;        for i:=l to r do        begin            uuu[i]:=0;            kkk[i]:=0;        end;        ef(l,m);        ef(m+1,r);    end;begin    readln(n);    for i:=1 to n do    begin        readln(x[i],y[i]);        ppp[x[i]]:=y[i];    end;    for i:=1 to n do    y[i]:=ppp[i];    ans:=n;    ef(1,n);    writeln(ans);end.
4 0
原创粉丝点击