D - Tiles for Bathroom
题目描述
给定一个 n ∗ n n*n n∗n 的矩阵 c c c ,对于 [ 1 , n ] [1,n] [1,n] 以内的每个 k k k ,求出大小为 k ∗ k k*k k∗k 的合法子矩阵数量。
如果一个矩阵中的元素种类不超过 q q q ,那么我们称这个矩阵合法。
数据范围与提示
1 ≤ n ≤ 1500 1\le n\le 1500 1≤n≤1500 , 1 ≤ q ≤ 10 1\le q\le 10 1≤q≤10 1 ≤ c i , j ≤ n 2 1\le c_{i,j}\le n^2 1≤ci,j≤n2 。
思路
首先是问题转换,相当于要我们求每个位置可以扩展到的最大正方形的大小 a n s i , j ans_{i,j} ansi,j (不管是看左上角还是看右下角),然后由于合法矩阵有包含关系,即在位置 i , j i,j i,j 上若有大小为 k k k 的正方形矩阵合法,那么大小为 k − 1 k-1 k−1 的矩阵同样合法,以此类推。所以最后只要在统计答案时求一个后缀和即可。接下来就是考虑怎么求每个 a n s i , j ans_{i,j} ansi,j 的问题。
二维的问题的解决方案一般由一维扩展而来,对于这题,不妨先考虑一维的情况。
做法一(官解): 考虑记录每个位置往左出现的最近的 q + 1 q+1 q+1 种元素第一次(也就是最靠右的)出现的位置,那么每个位置只需要一个长度为 q + 1 q+1 q+1 的序列,每次把 i − 1 i-1 i−1 处的序列复制过来,插入一个值然后 O ( q ) O(q) O(q) 维护即可。答案即为第 q + 1 q+1 q+1 个位置右边的区间。
那么这种方法怎么扩展到二维呢?这里提到一个新概念:切比雪夫距离。
在数学中,切比雪夫距离或是 L ∞ L^{\infty} L∞ 度量,是向量空间中的一种度量,二个点之间的距离定义是其各坐标数值差绝对值的最大值。
此时把一维中坐标之间的距离换成切比雪夫距离,正好可以求出我们想要的最大正方形(至于为什么,请简单地感性理解)。
这时不是从 i − 1 , j i-1,j i−1,j 或 i , j − 1 i,j-1 i,j−1 转移过来,而只能从 i − 1 , j − 1 i-1,j-1 i−1,j−1 ,因为这样才可以保证切比雪夫距离大小关系不变,这时还要合并上两边的横竖两条一维的序列(一维切比雪夫距离就是朴素的坐标之差绝对值),对每个位置 i , j i,j i,j 维护三条序列即可。
官方题解算的是左上角倒着枚,这里算的右下角,做法是完全一样的。
做法二: 一维情况还有一种做法,考虑左端点(上面的是考虑右端点),基于 a n s i − 1 − 1 ≤ a n s i ans_{i-1}-1\le ans_i ansi−1−1≤ansi 的性质,暴力枚举扩展边界,可以做到均摊 O ( 1 ) O(1) O(1) 。
看起来仿佛更优,但是扩展到二维就不太好做了。在一维上暴力扩展,每次复杂度零维 O ( 1 ) O(1) O(1) ,但在二维上暴力扩展,每次扩展复杂度就是一维 O ( n ) O(n) O(n) 。想要更快,还是得用到 q q q 的特性。
考虑到每次扩展要把两个一维区间信息合并,而每个区间显然只需要存 q q q 种元素的信息,区间过大可以直接判断不合法。为了让区间信息仍然能用前缀和“相减”得到,我们可以记录每个位置之前最近的 q q q 种元素,但每种元素的个数记录的却是在整个前缀和中出现的次数。这样用后面的集合减去与前面集合的交集,仍然可以得到正确的区间信息。
这种做法过于繁杂~~(我一开始就用的这方法,没调出来)~~,所以并不推荐,有兴趣的可以打一打试码力。其它做法都和官解大同小异。
代码
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<vector>
#include<queue>
#include<stack>
#define ll long long
#define MAXN 200005
#define uns unsigned
#define INF 0x3f3f3f3f
using namespace std;
inline ll read(){
ll x=0;bool f=1;char s=getchar();
while((s<'0'||s>'9')&&s>0){if(s=='-')f^=1;s=getchar();}
while(s>='0'&&s<='9')x=(x<<1)+(x<<3)+s-'0',s=getchar();
return f?x:-x;
}
int n,c[1505][1505],q,ans[1505][1505],pt[1505];
int f[1505][1505][13],a[1505][1505][13],b[1505][1505][13];
int num[2250005];
int cc[25];
inline int X(int s){return s/10000;}
inline int Y(int s){return s%10000;}
inline int S(int x,int y){return x*10000+y;}
//此处不用结构体是因为用了结构体一直出病毒,交CF上虽能编译但无法运行,所以只好哈希。
inline void mergg(int x,int y,int*a,int*b,int*cc){
int u=1,v=1,o=0;
while(u<=q+1&&a[u]&&v<=q+1&&b[v]){
if(max(x-X(a[u]),y-Y(a[u]))<=max(x-X(b[v]),y-Y(b[v])))
cc[++o]=a[u],u++;
else cc[++o]=b[v],v++;
}
while(u<=q+1&&a[u])cc[++o]=a[u],u++;
while(v<=q+1&&b[v])cc[++o]=b[v],v++;
cc[o+1]=0;
for(int l=1,r=1;r<=(q<<1)+2&&cc[r];r++){
if(!num[c[X(cc[r])][Y(cc[r])]])
num[c[X(cc[r])][Y(cc[r])]]++,cc[l]=cc[r],l++;
if(r>=l)cc[r]=S(0,0);
}
int k;
for(k=1;k<=(q<<1)+2&&cc[k];k++)
num[c[X(cc[k])][Y(cc[k])]]=0;
for(;k<=(q<<1)+2;k++)cc[k]=0;
}
int main()
{
n=read(),q=read();
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)c[i][j]=read();
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++){
a[i][j][1]=S(i,j),a[i][j][2]=0;
mergg(i,j,a[i][j],a[i][j-1],cc);
for(int k=1;k<=q+1&&cc[k];k++)a[i][j][k]=cc[k];
b[i][j][1]=S(i,j),b[i][j][2]=0;
mergg(i,j,b[i][j],b[i-1][j],cc);
for(int k=1;k<=q+1&&cc[k];k++)b[i][j][k]=cc[k];
mergg(i,j,f[i-1][j-1],a[i][j],cc);
for(int k=1;k<=q+1&&cc[k];k++)f[i][j][k]=cc[k];
mergg(i,j,f[i][j],b[i][j],cc);int k;
for(k=1;k<=q+1&&cc[k];k++)f[i][j][k]=cc[k];
for(;k<13;k++)f[i][j][k]=0;
ans[i][j]=min(min(i,j),max(i-X(f[i][j][q+1]),j-Y(f[i][j][q+1])));
pt[ans[i][j]]++;
}
for(int i=n-1;i>0;i--)pt[i]+=pt[i+1];
for(int i=1;i<=n;i++)printf("%d\n",pt[i]);
return 0;
}