问题 C: 奇袭
时间限制: 1 Sec 内存限制: 256 MB题目描述
由于各种原因,桐人现在被困在Under World(以下简称UW)中,而UW马上 要迎来最终的压力测试——魔界入侵。
唯一一个神一般存在的Administrator被消灭了,靠原本的整合骑士的力量 是远远不够的。所以爱丽丝动员了UW全体人民,与整合骑士一起抗击魔族。
在UW的驻地可以隐约看见魔族军队的大本营。整合骑士们打算在魔族入侵前 发动一次奇袭,袭击魔族大本营!
为了降低风险,爱丽丝找到了你,一名优秀斥候,希望你能在奇袭前对魔族 大本营进行侦查,并计算出袭击的难度。
经过侦查,你绘制出了魔族大本营的地图,然后发现,魔族大本营是一个N ×N的网格图,一共有N支军队驻扎在一些网格中(不会有两只军队驻扎在一起)。
在大本营中,每有一个k×k(1≤k≤N)的子网格图包含恰好k支军队,我们袭 击的难度就会增加1点。
现在请你根据绘制出的地图,告诉爱丽丝这次的袭击行动难度有多大。
输入
第一行,一个正整数N,表示网格图的大小以及军队数量。
接下来N行,每行两个整数,Xi,Yi,表示第i支军队的坐标。
保证每一行和每一列都恰有一只军队,即每一个Xi和每一个Yi都是不一样 的。
输出
样例输入
5
1 1
3 2
2 4
5 5
4 3
样例输出
10
提示
【样例解释】
显然,分别以(2,2)和(4,4)为左上,右下顶点的一个子网格图中有3支军队,
这为我们的难度贡献了1点。
类似的子网格图在原图中能找出10个。
【数据范围】
对于30%的数据,N ≤ 100
对于60%的数据,N ≤ 5000
对于100%的数据,N ≤ 50000
这道题当时看一眼还以为又是一个带权并查集的大坑,然后发现我只猜对了一半,这确实是个坑,不过貌似与带权并查集没啥关系。
开始的时候先打了一个n^3 log(n^2)的树状数组暴力期望目测30分,然后又打了一个严格n^2的单调队列暴力55分(虽然我期望60),考完后惊喜的发现加了一些剪枝的n^2可以水到91分……
这道题正解是线段树或分治,由于线段树打发我不会,我就只讲分治了。
我们可以发现这道题有一个极其优美的性质即每行每列只有一个敌人也就意味着我们没有必要去管那些没有敌人的位置。然后让我们分析一下时间复杂度,他的最大解是1250025000(所有敌人排成一个对角线),显然直接枚举每一个解都会炸也就是说所有复杂度大于等于O(合法方块数量)的都挂了。这道题也显然没有O(1)做法,那我们该咋搞呢?
我们可以发现,他是一个区间解,我们可以尝试一下分治这个本身时间复杂度为log n的想法,我们可以把每一个区间的解分成三个来源,左区间的解,右区间的解,区间跨过终点的解。很明显这三个不会重复,而且前两个直接就可以得到,难点在第三个来源上,对于第三个来源,我们又可以分为四种情况,区间最大值最小值在左侧(右侧),区间最大值在左(右),最小值在右(左),前两种我们可以通过类似于前缀后缀最大最小值的思想枚举左或右区间端点,然后O(1)的得到区间另一个端点,通过前后缀最大最小值是否合法判断是否算到答案之中,对于后两种就是本代码的核心了。
我们先以最小值在左最大值在右为例讲解,首先满足要求的区间一定是这样的:
max(a[mid +1]...a[right]) - min(a[left]...a[mid]) = right - left
通过移项可知
max(a[mid +1]...a[right]) - right = min(a[left]...a[mid]) - left
然后我们可以发现等式两边就没有直接联系了,借助天天爱跑步的思路,桶,我们存下每一个rmax[i]-i的值然后枚举左端点就可以了。值得注意的是rmax,rmin,lmin,lmax,都是具有优美的单调性质的,我们可以利用这一点再剪一下枝,同时,由于桶的存在,我们也就避免了一个个的去统计所有解,明显加快了时间。
对于最大值在左侧最小值在右侧我们只要转换一下就好了:
max(a[mid +1]...a[right]) + right = min(a[left]...a[mid]) + left
然后同理。
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 #include<iostream> 2 #include<cstdio> 3 #include<cstdlib> 4 #include<cstring> 5 #include<cmath> 6 #include<algorithm> 7 #include<queue> 8 #define N 50005 9 using namespace std; 10 int n,a[N],t[N*3+5]; 11 int lmin[N],rmin[N],lmax[N],rmax[N]; 12 int work2(int l,int r,int mid) 13 { 14 lmin[mid]=lmax[mid]=a[mid]; 15 rmin[mid+1]=rmax[mid+1]=a[mid+1]; 16 for(int i=mid-1;i>=l;i--) 17 { 18 lmin[i]=min(lmin[i+1],a[i]); 19 lmax[i]=max(lmax[i+1],a[i]); 20 } 21 for(int i=mid+2;i<=r;i++) 22 { 23 rmin[i]=min(rmin[i-1],a[i]); 24 rmax[i]=max(rmax[i-1],a[i]); 25 } 26 int ans=0; 27 for(int i=l;i<=mid;i++) 28 { 29 int to=i+lmax[i]-lmin[i]; 30 if(to<=r&&to>mid&&rmax[to]<lmax[i]&&lmin[i]<rmin[to]) ans++; 31 } 32 for(int i=mid+1;i<=r;i++) 33 { 34 int to=i-rmax[i]+rmin[i]; 35 if(to>=l&&to<=mid&&rmax[i]>lmax[to]&&rmin[i]<lmin[to]) ans++; 36 } 37 int p=mid+1,q=mid+1; 38 while(p<=r&&rmin[p]>lmin[l]) //min is on the left 39 { 40 t[rmax[p]-p+N]++; 41 p++; 42 } 43 while(q<=r&&rmax[q]<lmax[l]) //max is on the right 44 { 45 t[rmax[q]-q+N]--; 46 q++; 47 } 48 for(int i=l;i<=mid;i++) 49 { 50 while(p>mid+1&&rmin[p-1]<lmin[i]) 51 { 52 p--; 53 t[rmax[p]-p+N]--; 54 } 55 while(q>mid+1&&rmax[q-1]>lmax[i]) 56 { 57 q--; 58 t[rmax[q]-q+N]++; 59 } 60 if(t[lmin[i]-i+N]>0) 61 ans+=t[lmin[i]-i+N]; 62 } 63 for(int i=mid+1;i<=r;i++)t[rmax[i]-i+N]=0; 64 p=mid,q=mid; 65 while(p>=l&&lmin[p]>rmin[r])//min is on the right 66 { 67 t[lmax[p]+p+N]++; 68 p--; 69 } 70 while(q>=l&&lmax[q]<rmax[r]) //max is on the left 71 { 72 t[lmax[q]+q+N]--; 73 q--; 74 } 75 for(int i=r;i>mid;i--) 76 { 77 while(p<mid&&lmin[p+1]<rmin[i]) 78 { 79 p++; 80 t[lmax[p]+p+N]--; 81 } 82 while(q<mid&&lmax[q+1]>rmax[i]) 83 { 84 q++; 85 t[lmax[q]+q+N]++; 86 87 } 88 if(t[rmin[i]+i+N]>0) 89 { 90 ans+=t[rmin[i]+i+N]; 91 } 92 } 93 for(int i=l;i<=mid;i++) 94 { 95 t[lmax[i]+i+N]=0; 96 } 97 return ans; 98 } 99 int work(int l,int r) 100 { 101 if(l==r)return 1; 102 int mid=(l+r)/2; 103 int ans=work(l,mid)+work(mid+1,r); 104 105 ans+=work2(l,r,mid); 106 return ans; 107 } 108 int main() 109 { 110 scanf("%d",&n); 111 for(int i=1;i<=n;i++) 112 { 113 int x,y; 114 scanf("%d%d",&x,&y); 115 a[x]=y; 116 } 117 //cout<<endl; 118 printf("%d\n",work(1,n)); 119 return 0; 120 }
其实还有一个黑科技 reverse函数将整个区间左右调换,省了不少代码量,只是复杂度多了一个log n。
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 #include<iostream> 2 #include<cstdio> 3 #include<cstdlib> 4 #include<cstring> 5 #include<cmath> 6 #include<algorithm> 7 #include<queue> 8 #define N 50005 9 using namespace std; 10 int n,a[N],t[N*2+5]; 11 int lmin[N],rmin[N],lmax[N],rmax[N]; 12 int work2(int l,int r,int mid) 13 { 14 lmin[mid]=lmax[mid]=a[mid]; 15 rmin[mid+1]=rmax[mid+1]=a[mid+1]; 16 for(int i=mid-1;i>=l;i--) 17 { 18 lmin[i]=min(lmin[i+1],a[i]); 19 lmax[i]=max(lmax[i+1],a[i]); 20 } 21 for(int i=mid+2;i<=r;i++) 22 { 23 rmin[i]=min(rmin[i-1],a[i]); 24 rmax[i]=max(rmax[i-1],a[i]); 25 } 26 int ans=0; 27 for(int i=l;i<=mid;i++) 28 { 29 int to=i+lmax[i]-lmin[i]; //max,min is on the left 30 if(to<=r&&to>mid&&rmax[to]<lmax[i]&&lmin[i]<rmin[to]) ans++; 31 } 32 int p=mid+1,q=mid+1; 33 while(p<=r&&rmin[p]>lmin[l]) //min is on the left 34 { 35 t[rmax[p]-p+N]++; 36 p++; 37 } 38 while(q<=r&&rmax[q]<lmax[l]) //max is on the right 39 { 40 t[rmax[q]-q+N]--; 41 q++; 42 } 43 for(int i=l;i<=mid;i++) 44 { 45 while(p>mid+1&&rmin[p-1]<lmin[i]) 46 { 47 p--; 48 t[rmax[p]-p+N]--; 49 } 50 while(q>mid+1&&rmax[q-1]>lmax[i]) 51 { 52 q--; 53 t[rmax[q]-q+N]++; 54 } 55 if(t[lmin[i]-i+N]>0) 56 ans+=t[lmin[i]-i+N]; 57 } 58 for(int i=mid+1;i<=r;i++)t[rmax[i]-i+N]=0; 59 return ans; 60 } 61 int work(int l,int r) 62 { 63 if(l==r)return 1; 64 int mid=(l+r)/2; 65 int ans=work(l,mid)+work(mid+1,r); 66 ans+=work2(l,r,mid); 67 reverse(a+l,a+r+1); 68 ans+=work2(l,r,mid-((r-l+1)%2)); 69 reverse(a+l,a+r+1); 70 return ans; 71 } 72 int main() 73 { 74 scanf("%d",&n); 75 for(int i=1;i<=n;i++) 76 { 77 int x,y; 78 scanf("%d%d",&x,&y); 79 a[x]=y; 80 } 81 printf("%d\n",work(1,n)); 82 return 0; 83 }