BZOJ2351Matrix 矩阵hash详解

4753: Lydsy2351 Matrix

Time Limit: 10 Sec  Memory Limit: 128 MB
Submit: 26  Solved: 15
[Submit][Status][Web Board]

Description

给定一个M行N列的01矩阵,以及Q个A行B列的01矩阵,你需要求出这Q个矩阵哪些在原矩阵中出现过。
所谓01矩阵,就是矩阵中所有元素不是0就是1。

Input

输入文件的第一行为M、N、A、B,参见题目描述。
接下来M行,每行N个字符,非0即1,描述原矩阵。
接下来一行为你要处理的询问数Q。
接下来Q个矩阵,一共Q*A行,每行B个字符,描述Q个01矩阵。
A ≤ 100

Output

你需要输出Q行,每行为0或者1,表示这个矩阵是否出现过,0表示没有出现过,1表示出现过。

Sample Input

3 3 2 2
111
000
111
3
11
00
11
11
00
11

Sample Output

1
0
1


题意很好理解,暴力做法更容易理解。直接枚举每一个点,个数1000*1000,(这里貌似没给n,m的数据范围,但我隐约记得bzoj上给的1000以内),每个点都可以向外拓展100*100个,以及还有1000次的询问,总共的复杂度差不多就是这些乘起来。结果这题给的时限是
10秒钟,说不定可以过?

非暴力解法就是矩阵hash,当然这种解法应该也不是最快的,但可以拿来练练hash。
算法分析:
首先可以看题目,发现尽管有Q个询问,但是每个询问的矩阵大小是一定的,所以可以先把原矩阵中所有这个大小的矩阵的hash值存下来,存法类似于二维前缀和,每次询问时以O(k)(k为这么大的子矩阵个数)的时间复杂度来查询是否存在即可。

这里说一下:这里的查询可以再优化一下,可以结合一下链表。我们在hash时选的是264为%的值,然而数组的下标是肯定开不了这么大的,如果我们想要把它和类似于桶排的算法O(1)询问,就可以把%的值改小到数组能开下,再用邻接表的方式存储
hash值,询问时速度会加快,但建链时会慢,所以具体是加快还是减慢也不好说,由于这里的查询最大只有1000次,就直接查就行

以上就是思路,代码如下:
 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 using namespace std;
 5 typedef unsigned int ull;
 6 ull val[1000010],p1[1003],p2[1003],hash_[1001][1001],hash_2[101][101];
 7 ull has,H;
 8 int p=0;int n,m,a,b;
 9 const ull Pi=131,Pj=233;
10 inline void reset_p()
11 {
12     p1[0]=1;
13     p2[0]=1;
14     for(int i=1;i<=1000;i++)
15     {
16         p1[i]=p1[i-1]*233;
17         p2[i]=p2[i-1]*131;
18     }
19 }
20 inline void _hash()
21 {
22      for (int i=1;i<=n;i++)
23         for (int j=1;j<=m;j++)  hash_[i][j]=hash_[i-1][j]*Pi+hash_[i][j];
24     for (int i=1;i<=n;i++)
25         for (int j=1;j<=m;j++)  hash_[i][j]=hash_[i][j-1]*Pj+hash_[i][j];
26 }
27 inline ull _hash2()
28 {
29     for (int i=1;i<=a;i++)
30         for (int j=1;j<=b;j++) hash_2[i][j]=hash_2[i-1][j]*Pi+hash_2[i][j];
31     for (int i=1;i<=a;i++)
32         for (int j=1;j<=b;j++) hash_2[i][j]=hash_2[i][j-1]*Pj+hash_2[i][j];
33     return hash_2[a][b];
34 }
35 int main()
36 {
37     reset_p();
38     
39     scanf("%d%d%d%d",&n,&m,&a,&b);
40     
41     for(int i=1;i<=n;i++)
42         for(int j=1;j<=m;j++)
43             scanf("%1u",&hash_[i][j]);
44     _hash();
45     for(int i=a;i<=n;i++)
46     for(int j=b;j<=m;j++)
47     {
48         has=hash_[i][j];has-=hash_[i-a][j]*p2[a];has-=hash_[i][j-b]*p1[b];has+=hash_[i-a][j-b]*p2[a]*p1[b];
49         val[++p]=has;  
50     }
51     int q,flag=0;
52     char useless;
53     scanf("%d",&q);
54     for(int v=1;v<=q;v++)
55     {    
56         for(int i=1;i<=a;i++)
57             for(int j=1;j<=b;j++)
58                 scanf("%1u",&hash_2[i][j]);
59         H=_hash2();
60         
61         for(int j=1;j<=p-1;j++)
62         {    
63             if(val[j]==H)
64                 {
65                     if(v!=q)printf("1\n");
66                     else printf("1");
67                     flag=1;
68                     break;
69                 }
70         }
71         if(!flag)
72             if(v!=q)printf("0\n");
73             else printf("0");
74         flag=0;
75     }
76     return 0;
77 }
 

 

 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值