C. 奇袭
题目描述
输入格式
输出格式
样例
数据范围与提示
一道及其神仙的题,考试时只想到了暴力,复杂度$n^3log^2n$,用二维树状数组记录前缀和(有什么意义……),最后T27(说好30呢……)。
1 LL ans=0; 2 for(int k=2;k<n;k++) 3 for(int i=1;i<=n-k+1;i++) 4 for(int j=1;j<=n-k+1;j++) 5 if(ask(i+k-1,j+k-1)-ask(i-1,j+k-1)-ask(i+k-1,j-1)+ask(i-1,j-1)==k)ans++; 6 printf("%lld ",ans+n+1);
然后是$n^2$的方法,由于x,y没有重复,我们把x当作数组下标,就转化成了一维的,那么问题就转化成了:对于a数组,有多少个区间[L,R]满足区间是连续一段数(可以是乱序),进而转化为了:有多少个区[L,R] 满足max(a[L],……a[R])-min(a[L],……,a[R])=R-L,复杂度$n^2$,代码实现:
1 LL ans=0,ma=-0x7fffffff,mi=0x7fffffff; 2 for(int L=1;L<=n;L++) 3 { 4 ma=-0x7fffffff,mi=0x7fffffff; 5 for(int R=L;R<=n;R++) 6 { 7 ma=max(ma,h[R]),mi=min(mi,h[R]); 8 if(R-L==ma-mi)ans++; 9 } 10 }
然后就是及其神仙的$nlogn$正解了:
分治,区间[L,R]的答案=[L,mid]+[mid+1,R]+跨mid的答案,对于前两种直接递归到L=R return 1即可,主要是最后一种。
首先预处理出[L,mid]中的各个点i,区间[i,mid]的最大值和最小值,以及区间[mid+1,R]中各个点i,区间[mid+1,i]的最大值和最小值(注意mid+1)。以下ma[i],如果i<=mid,则表示[i,……,mid]最大值,否则表示[mid+1,……,i]最大值,mi[i]同理。
代码实现:
1 ma[mid]=mi[mid]=h[mid]; 2 for(int i=mid-1;i>=L;i--) 3 mi[i]=min(mi[i+1],h[i]), 4 ma[i]=max(ma[i+1],h[i]); 5 ma[mid+1]=mi[mid+1]=h[mid+1]; 6 for(int i=mid+2;i<=R;i++) 7 mi[i]=min(mi[i-1],h[i]), 8 ma[i]=max(ma[i-1],h[i]);
跨区间的答案分两种情况:
1.这个区间的最小值和最大值都在[L,mid]段,但是这个区间的跨mid,枚举区间左端点,由$r-l=ma[l,r]-mi[l,r]$并且最大值最小值都在[L,mid]段,所以$r-l=ma[l]-mi[l]$,枚举左端点,则r=l+ma[l]-mi[l],判断r
是否符合条件,若符合,ans++,不符合的情况:r>R,r<=mid(因为要跨区间),mi[r]<mi[l](这样最小值就不在左边了),ma[r]>mi[l]同理。
1 for(int l=L;l<=mid;l++) 2 { 3 int r=ma[l]-mi[l]+l; 4 if(r>R)continue; 5 if(r<=mid)continue; 6 if(ma[r]>ma[l])continue; 7 if(mi[r]<mi[l])continue; 8 ans++; 9 }
2.这个区间的最小值和最大值都在[mid+L,R]段,这个区间跨mid,枚举区间又端点即可,同上。
3.区间最小值在[L,mid],最大值在[mid+1,R],则满足$ma[r]-mi[l]=r-l$,移项得$ma[r]-r=mi[l]-l$,此时式子的左右两边就没有什么关系了,然后用到了和BSGS相似的思路,将ma[i]-i放在桶里(即把其当作数组下标),通过两个指针,r1=r2=mid+1,ma与mi是严格单调的,首先将r1(r1<=R)不断++,若满足mi[r1]>mi[L],则tong[ma[r1]-r1]++,对于整个[L,mid],这些都是满足mi[r1]>mi[L],即保证最小值在左区间,然后将r2不断++,若满足ma[r2]<ma[L],则tong[ma[r2]-r1]--,因为此时不满足最大值在右区间。此时tong中的数值都会满足ma[i]>ma[L],mi[i]<mi[L],然后我们就可以统计答案了,枚举左端点l,ans+=tong[mi[l]-l](mi[l]-l=ma[r]-r),但是这样就够了吗?并不够,因为桶中的数据是满足ma[i]>ma[L],mi[i]<mi[L],但是由于是从左到右枚举左端点,我们只需要它满足ma[i]>ma[l]&&mi[i]<ma[l]即可,而ma,mi是单调的,显然ma[l]<=ma[L],mi[l]>=mi[L],随着l的枚举,ma[l]会越来越小,mi[l]则会越来越大,符合条件$mi[r1]>mi[L],ma[r2]>ma[L]$的会越来越多,所以r1,r2还要继续移动,将在l-1时不符合条件但是在l时符合条件的放进去,然后就可以ans+=tong[mi[l]-l]了。有几个小细节要注意,考虑到ma[i]-i会出负数,所以tong下标要+n(当然你也可以用指针进行一些骚操作使负数合法),tong不要memset,其实不会影响正确性只是会T27……
还有就是如果tong的值是负数时就不要加了,判断的时候要写if(...>0)(不要像我一样沙雕地不写>0,非0为真,负数也为真)。
1 int r1=mid+1,r2=mid+1; 2 while(r1<=R&&mi[L]<mi[r1]) 3 { 4 tong[n+ma[r1]-r1]++; 5 r1++; 6 } 7 while(r2<=R&&ma[L]>ma[r2]) 8 { 9 tong[n+ma[r2]-r2]--; 10 r2++; 11 } 12 for(int l=L;l<=mid;l++) 13 { 14 while(r1>mid+1&&mi[r1-1]<mi[l]) 15 r1--,tong[n+ma[r1]-r1]--; 16 while(r2>mid+1&&ma[r2-1]>ma[l]) 17 r2--,tong[n+ma[r2]-r2]++; 18 if(tong[n+mi[l]-l]>0)ans+=tong[n+mi[l]-l]; 19 } 20 for(int i=mid+1;i<=R;i++)tong[n+ma[i]-i]=0;
4.最大值在左边最小值在右边的情况,和上边就很类似了,只是式子要换一下,$ma[l]-mi[r]=r-l$,移项得$ma[l]+l=mi[r]+r$,其余的就大概相同了。
当然你也可以用一下黑科技区间反转,但是要判断一下mid。