2021年中国大学生程序设计竞赛女生专场 gym103389F 地图压缩

F. 地图压缩

你正在参与一款2D游戏的地图绘制,你拥有的美术素材是一张 n×n 的像素图片,从上到下依次编号为第 1 行到第 n 行,从左往右依次编号为第 1 列到第 n 列,其中第 i 行第 j 列的像素坐标为 (i,j),它的内容可以用一个小写字母 pi,j 来表示。
你希望从这张素材中裁剪出一个连续的矩形区域作为游戏的地图。你进行了 q 次尝试,每次尝试中你将会选择以 (x1,y1) 为左上角、(x2,y2) 为右下角的矩形部分(包括端点)作为游戏的地图。通过不断地尝试,你发现可以通过应用"四方连续"的方法来压缩地图。"四方连续纹样"是指一个单位矩形纹样向上下左右四个方向反复连续循环排列所产生的纹样,例如样例中以 (1,1) 和 (3,7) 为对角线端点的矩形可以看作以 (1,1) 和 (2,3) 为对角线端点的矩形向上下左右四个方向反复连续循环排列所产生的,该矩形包含 2×3=6 个像素点。注意,纹样在生成地图的过程中可以超出边缘,超出边缘的部分将被忽略。
请写一个程序,对于每次尝试的地图,找到包含像素点数量最少的单位矩形纹样,使得它利用四方连续可以生成该次尝试的地图。
Input
第一行包含两个正整数 n 和 q (1≤n≤2000, 1≤q≤10000),分别表示素材的尺寸和尝试的次数。
接下来 n 行,第 i 行包含一个长度为 n 的小写字符串,其中第 j 个字符表示 pi,j。
接下来 q 行,每行四个正整数x1,y1,x2,y2 (1≤x1≤x2≤n, 1≤y1≤y2≤n),依次表示每次尝试的矩形的对角线的端点坐标。
Output
输出 q 行,第 i 行输出一个正整数,表示第 i 次尝试对应的最小单位纹样包含的像素点数量。

思路:
对于一个询问区间,横着和竖着的周期性互不干扰,所以可以分别求其最小周期,然后相乘。
对于横着的最小周期,我们可以把纵向维度压扁,转化为一个一维问题。压扁的时候可以用字符串哈希,预处理出哈希值的前缀和,询问的时候把区间内的每一列压成一个哈希值。
转化为一维问题后,可以再用一次哈希,求出最大公共前后缀,用区间长减去这个最大值即最小周期。(也可用kmp处理)
注意两次hash的P最好不相同。(相同会WA,原因不明呜呜)

#include<bits/stdc++.h>
using namespace std;

#define maxn 2000
#define maxQ 10000
#define ull unsigned long long


string a[maxn+5];

const int P=131,_P=1331;
ull p[maxn+5],_p[maxn+5];
ull hashrow[maxn+5][maxn+5],hashcol[maxn+5][maxn+5];

ull drow[maxn+5],dcol[maxn+5];

int main() {
	
	int n,Q;
	cin>>n>>Q;
	for(int i=1;i<=n;i++) {
		cin>>a[i];
		a[i]=" "+a[i];
	}
	
	p[0]=_p[0]=1;
	for(int i=1;i<=n;i++) p[i]=p[i-1]*P,_p[i]=_p[i-1]*_P;
	
	for(int i=1;i<=n;i++) {
		for(int j=1;j<=n;j++) {
			hashrow[i][j]=hashrow[i-1][j]*P+a[i][j]-'a';
			hashcol[i][j]=hashcol[i][j-1]*P+a[i][j]-'a';
		}
	}
	
	while(Q--) {
		int x1,y1,x2,y2;
		cin>>x1>>y1>>x2>>y2;
		
		for(int j=y1;j<=y2;j++) 
			drow[j]=hashrow[x2][j]-hashrow[x1-1][j]*p[x2-x1+1];
		for(int i=x1;i<=x2;i++) 
			dcol[i]=hashcol[i][y2]-hashcol[i][y1-1]*p[y2-y1+1];
		
		ull s1=0,s2=0,ans1=-1,ans2=-1;
		for(int j=0;y1+j<y2;j++) {
			s1=s1*_P+drow[y1+j];
			s2=drow[y2-j]*_p[j]+s2;
			if(s1==s2) {
				ans1=j;
			}
		}
		s1=s2=0;
		for(int i=0;x1+i<x2;i++) {
			s1=s1*_P+dcol[x1+i];
			s2=dcol[x2-i]*_p[i]+s2;
			if(s1==s2) {
				ans2=i;
			}
		}
		
		cout<<(y2-y1-ans1)*(x2-x1-ans2)<<endl;		
	}
	
	return 0;
}
  • 5
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值