CF1500D Tiles for Bathroom——数组合并思维题

D - Tiles for Bathroom

题目描述

给定一个 n ∗ n n*n nn 的矩阵 c c c ,对于 [ 1 , n ] [1,n] [1,n] 以内的每个 k k k ,求出大小为 k ∗ k k*k kk 的合法子矩阵数量。

如果一个矩阵中的元素种类不超过 q q q ,那么我们称这个矩阵合法。

数据范围与提示

1 ≤ n ≤ 1500 1\le n\le 1500 1n1500 , 1 ≤ q ≤ 10 1\le q\le 10 1q10 1 ≤ c i , j ≤ n 2 1\le c_{i,j}\le n^2 1ci,jn2

思路

首先是问题转换,相当于要我们求每个位置可以扩展到的最大正方形的大小 a n s i , j ans_{i,j} ansi,j (不管是看左上角还是看右下角),然后由于合法矩阵有包含关系,即在位置 i , j i,j i,j 上若有大小为 k k k 的正方形矩阵合法,那么大小为 k − 1 k-1 k1 的矩阵同样合法,以此类推。所以最后只要在统计答案时求一个后缀和即可。接下来就是考虑怎么求每个 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 i1 处的序列复制过来,插入一个值然后 O ( q ) O(q) O(q) 维护即可。答案即为第 q + 1 q+1 q+1 个位置右边的区间。

那么这种方法怎么扩展到二维呢?这里提到一个新概念:切比雪夫距离

在数学中,切比雪夫距离或是 L ∞ L^{\infty} L 度量,是向量空间中的一种度量,二个点之间的距离定义是其各坐标数值差绝对值的最大值。

此时把一维中坐标之间的距离换成切比雪夫距离,正好可以求出我们想要的最大正方形(至于为什么,请简单地感性理解)。

这时不是从 i − 1 , j i-1,j i1,j i , j − 1 i,j-1 i,j1 转移过来,而只能从 i − 1 , j − 1 i-1,j-1 i1,j1 ,因为这样才可以保证切比雪夫距离大小关系不变,这时还要合并上两边的横竖两条一维的序列(一维切比雪夫距离就是朴素的坐标之差绝对值),对每个位置 i , j i,j i,j 维护三条序列即可。

官方题解算的是左上角倒着枚,这里算的右下角,做法是完全一样的。

做法二: 一维情况还有一种做法,考虑左端点(上面的是考虑右端点),基于 a n s i − 1 − 1 ≤ a n s i ans_{i-1}-1\le ans_i ansi11ansi 的性质,暴力枚举扩展边界,可以做到均摊 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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值