LOJ6698 一键挖矿 优美的分治

· · 题解

LOJ6698 一键挖矿
Oiclass 挖矿
Oiclass 万猪拱塔

显然对于在范围内的方块有 $(\max(x)-\min(x)+1)(max(y)-min(y)+1)=r-l+1$。 此思路来源于 [@ty_Ray](https://www.luogu.com.cn/user/726899) 大佬。 考虑分治,记左/右的坐标极值为 $l/r+ma/mi+x/y$,分以下几种情况: 1. $lmax=rmax$,$lmix=rmix$,$lmay=rmay$,$lmiy=rmiy$。即边界相同。 2. $lmax\le rmax$,$lmix\ge rmix$,$lmay\le rmay$,$lmiy\ge rmiy$,$l,r$ 可以反过来。即呈包含关系。 3. $lmax=rmax$,$lmix=rmix$,$lmay<rmay$,$lmiy<rmiy$,$x,y$、$l,r$ 都可以反过来。即呈平行关系。 因为情况 $2$ 有算重部分,所以答案应为 $3+2-1$。 就情况 $3$ 比较麻烦。假设情况为上面所列举的,由条件可得 $(rmax-rmix+1)(rmay-lmiy+1)=r-l+1$。 令 $sum=(rmax-rmix+1)$,转化为 $sum(rmay+1)-(r-mid)=sum·lmiy+(mid-l+1)$。 每次处理 $rmax,rmix$ 相同的一段,找到对应的 $lmax=rmax,lmix=rmix$ 的一段,在这条限制的基础上,移动右端点,符合 $y$ 限制的左端点在一个区间里,用桶查找满足式子的限制的左端点数量。 时间复杂度小常数 $O(n\log n)$。 ```cpp #include<bits/stdc++.h> #define y1 yyyy//ty_ray的细节操作 typedef long long ll; using namespace std;//知道这是快读就行了 int read(){char c;bool flag=1;while(c=getchar(),c<'0'||c>'9') if(c=='-') flag=0;int x=c^'0';while(c=getchar(),c>='0'&&c<='9') x=x*10+(c^'0');return flag?x:-x;} const int N=2e5; int n,m,x[N+5],y[N+5],x1[N+5],y1[N+5],x2[N+5],y2[N+5],w[N*2]; ll ans;// mix miy max may 桶 void _calc(int l,int r,int mid,int x1[],int x2[],int y1[],int y2[]){//有点麻烦的情况3 int sum,j=mid,k=mid,K; auto val=[&](int k){return sum*(y1[k])+(mid-k+1);}; for(int i=mid+1;i<=r;i++){ if(i==mid+1||x1[i]!=x1[i-1]||x2[i]!=x2[i-1]){ //每次处理 $rmax,rmix$ 相同的一段,找到对应的 $lmax=rmax,lmix=rmix$ 的一段 while(j>k) --w[val(j--)]; while(l<=j&&(x1[i]<x1[j]||x2[j]<x2[i])) --j; sum=x2[j]-x1[j]+1,K=k=j; while(l<K&&x1[i]==x1[K-1]&&x2[i]==x2[K-1]) --K; } if(K>=l&&x1[i]==x1[K]&&x2[i]==x2[K]){ //移动右端点,符合 $y$ 限制的左端点在一个区间里,用桶查找满足式子的限制的左端点数量 while(k>=K&&y2[k]<y2[i]) ++w[val(k--)]; while(j>k&&y1[i]<=y1[j]) --w[val(j--)]; ans+=w[sum*(y2[i]+1)-(i-mid)]; } } while(j>k) --w[val(j--)]; } void calc(int l,int r,int mid,bool fl){ _calc(l,r,mid,x1,x2,y1,y2),_calc(l,r,mid,y1,y2,x1,x2);//x,y 翻转 for(int i=mid+1;i<=r;++i){//普通的情况2 int p=mid+1-((x2[i]-x1[i]+1)*(y2[i]-y1[i]+1)-(i-mid)); if(l<=p&&p<=mid&&x1[i]<=x1[p]&&x2[p]<=x2[i]&&y1[i]<=y1[p]&&y2[p]<=y2[i]) ++ans; } } void solve(int l,int r){ if(l==r) return ++ans,void(); int mid=(l+r)>>1; solve(l,mid),solve(mid+1,r); x1[mid]=x2[mid]=x[mid],y1[mid]=y2[mid]=y[mid]; for(int i=mid-1;i>=l;--i) x1[i]=min(x1[i+1],x[i]),x2[i]=max(x2[i+1],x[i]),y1[i]=min(y1[i+1],y[i]),y2[i]=max(y2[i+1],y[i]); x1[mid+1]=x2[mid+1]=x[mid+1],y1[mid+1]=y2[mid+1]=y[mid+1]; for(int i=mid+2;i<=r;++i) x1[i]=min(x1[i-1],x[i]),x2[i]=max(x2[i-1],x[i]),y1[i]=min(y1[i-1],y[i]),y2[i]=max(y2[i-1],y[i]); for(int i=mid+1;i<=r;++i){//普通的情况1 int p=mid+1-((x2[i]-x1[i]+1)*(y2[i]-y1[i]+1)-(i-mid)); if(l<=p&&p<=mid&&x1[i]==x1[p]&&x2[p]==x2[i]&&y1[i]==y1[p]&&y2[p]==y2[i]) --ans; } calc(l,r,mid,0); reverse(x1+l,x1+1+r),reverse(y1+l,y1+1+r),reverse(x2+l,x2+1+r),reverse(y2+l,y2+1+r); calc(l,r,l+(r-mid)-1,1);//l,r 翻转 } int main(){ n=read(),m=read(); for(int i=1;i<=n;++i) for(int j=1,s;j<=m;++j) s=read(),x[s]=i,y[s]=j; solve(1,n*m); printf("%lld",ans); } ```