bzoj 2462 [BeiJing2011]矩阵模板

2462: [BeiJing2011]矩阵模板

Time Limit: 2 Sec   Memory Limit: 128 MB
Submit: 715   Solved: 314
[ Submit][ Status][ Discuss]

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矩阵。 
 

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

HINT

对于100%的数据,A  < =  100。

Source


题解:二维hash 或 kmp 算法

这道题上午测试的时候写的kmp,大体上走的路线是对的,但是在判断子矩阵是想麻烦了,于是后两组超时了。

在某些神犇的提示下,又想了想,然后改了改,就AC了

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int n,m,a,b,q;
char s[1003][1003],s1[1003][1003];
int next[1003][1003],pos[10000];
void calc(char x[],int k)//对于小矩阵的每一行都建立失配函数
{
  next[k][0]=-1; int j;
  for (int i=0;i<b;i++)
   {
   	j=next[k][i];
   	while (j!=-1&&x[i]!=x[j]) j=next[k][j];
   	next[k][i+1]=++j;
   }
}
int makep(int x,char k[])
{
  int i=0; int j=0;
  while (i<m)
  {
    if (j==-1||k[i]==s1[x][j])
     i++,j++;
    else
     j=next[x][j];
    if (j==b)
     return true;
  }
  return false;
}
int makep1(char x[],int now,int k)//从x[]中的now位向后搜索,找到第一个可以匹配的位置,返回匹配的第一个位置
{
  int i=now; int j=0;
  while (i<m)
  {
    if (j==-1||x[i]==s1[k][j])
     i++,j++;
    else
     j=next[k][j];
    if (j==b)  return i-b+1;
  }
  return -1;
}
int main()
{
  scanf("%d%d%d%d",&n,&m,&a,&b);
  for (int i=1;i<=n;i++)
   scanf("%s",s[i]);
  scanf("%d",&q);
  for (int i=1;i<=q*a;i++)
    scanf("%s",s1[i]);
  for (int i=1;i<=q*a;i++)
   calc(s1[i],i);
  if (a==1)
   {
    for (int j=1;j<=q;j++)//如果小矩阵只有一行的话,直接让他与大矩阵的每行匹配就可以了,匹配上后就不用继续匹配了。
    {
     bool p=false;
     for (int i=1;i<=n;i++)
      if (makep(j,s[i]))
      {
       printf("1\n"),p=true;
       break;
      }
    if (!p) printf("0\n");
    }
    return 0;
   }
  else
   {
   	for (int i=1;i<=q;i++)
   	{
   	 bool pd=false;
   	 for (int j=1;j<=n;j++)//先枚举大矩阵的每一行,让小矩阵从当前行开始逐行匹配
   	  {
   	   bool p=true; int now=0;
   	   while (p&&!pd)
   	   {
   	   	for (int k=1;k<=a;k++)
   	   	{
   	   	  pos[k]=makep1(s[j+k-1],now,a*(i-1)+k);
   	   	  if (pos[k]==-1)//如果某一行没有匹配上,那么说明当前行是不合法的,直接跳转到当前行的下一行,重新匹配
   	   	   {
   	   	   	 p=false;
   	   	   	 break;
   	   	   }
   	   	}
   	   	if (!p)  break;  now=0; int num=0;
   	   	for (int k=1;k<=a;k++)
   	   	 {
   	   	   if (pos[k]==pos[1]) num++;//有可能小矩阵的每一行都匹配上了,但是他们匹配上的位置不在同一列,那么这也是不合法的
		   now=max(now,pos[k]); //now 用来记录下一次当前行开始重新匹配的位置,因为前面的都已经匹配过了,也就是不可能存在合法的子矩阵了,那么直接从所有匹配中找出最靠后的位置,从那里进行匹配即可。
   	   	 }
   	   	if (num==a)
   	   	 {
   	   	  printf("1\n");
   	   	  pd=true;
   	   	  break;
   	   	 }
   	   }
   	   if (pd) break;
   	  }
   	 if (!pd) printf("0\n");
   	}
   }
  
}

昨天学习了一下hash 算法,今天调了一上午的hash 终于AC了。T_T

hash 方法:把大矩阵中所有可能的小矩阵都转换成一个数,存储在hash 表里,每次查表就好了。

那么如何把小矩阵转换成一个数呢?昨天请教了一下神犇,神犇说你先把小矩阵的每一行看成一个二进制,然后转换成十进制,这样你就得到了a个十进制数,然后你确定一个字符基s(就是想办法把那a个数,看成a位,然后把他们转化成一个s进制数,一般s进制可以是一个足够大的质数)。

然后傻逼的我,开始了漫长AC路。

先是TLE,于是重新写。(友情提示:这道题貌似用map 映射会很慢,会TLE,当然如果你足够神,代码时间复杂度足够优越,也可以试试)

然后开始各种WA,好不容易调过了小数据,结果一上大数据,就开始全出0。最后看学长的代码,发现貌似是unsigned long long  的问题,学长用的long long 就是对的,难道炸飞的方式也有影响?于是我开始各种乱搞,最终得出一个结论,就是unsigned int 或unsigned long long 是可以自然溢出的,所有它本身得到的就是一个已经取模后的结果,那么我们在存储时就直接让他炸飞好了,不用取模来防止自然溢出。但是如果用long long 如果炸飞了,会出现负数之类的,所有我们可以通过对一个大质数取模的方式来进行hash 。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define  p  1000000007
using namespace std;
int n,m,a,b,tot;
char s[1003];
int mat[1003][1003],ok[1003][1003];
long long sum[1000],ch[1003][1003],sh[1003][1003],data[1000003],maxn;
long long mi,base;
int main()
{
 freopen("matrix.in","r",stdin);
 freopen("matrix.out","w",stdout);
 scanf("%d%d%d%d",&n,&m,&a,&b);
 for (int i=0;i<n;i++)
 {
  scanf("%s",s);
  for (int j=0;j<m;j++) mat[i][j]=s[j]-'0';
 }
 mi=1; base=1;
 for (int i=1;i<=b;i++)  mi=(mi<<1)%p; 
 for (int i=1;i<=a;i++)  base=(base*mi)%p;  
 for (int i=0;i<n;i++)
 {
  for (int j=1;j<=b;j++) 
    ch[i][m-b]=((ch[i][m-b]<<1)+mat[i][m-j])%p;//先把后b位搞成一个二进制,即  mat[i][x]*2^0+mat[i][x+1]*2^1....mat[i][x+b-1]*2^b-1 ,这样之后便于转移
  for (int j=m-b-1;j>=0;j--)
  {
   ch[i][j]=((ch[i][j+1]<<1)%p-mat[i][j+b]*mi+mat[i][j])%p;//ch[i][j]中存储的是从第j位开始向后B个字符转成十进制后的答案,每次把前一次的答案都左移一位,然后减去最靠后的一位×2^b,在加上第j位
  }
 }
 for (int i=m-b;i>=0;i--)
 {
   for (int j=1;j<=a;j++)
    sh[n-a][i]=((sh[n-a][i]*mi)+ch[n-j][i])%p;//以2^b为字符基,处理方式与上面几乎相同。
   data[tot++]=(sh[n-a][i]+p)%p;
   for (int j=n-a-1;j>=0;j--)
   {
    sh[j][i]=(sh[j+1][i]*mi-ch[j+a][i]*base+ch[j][i])%p;
    data[tot++]=(sh[j][i]+p)%p;
   }
 }
  sort(data,data+tot);
  tot=unique(data,data+tot)-data;
  int t; scanf("%d",&t);
  for (int i=1;i<=t;i++)
  {
  	for (int j=0;j<a;j++)
  	{
  	 scanf("%s",s);
  	 for (int k=0;k<b;k++) ok[j][k]=s[k]-'0';
  	}
  	int ans=0;
  	for (int j=a-1;j>=0;j--)
  	for (int k=b-1;k>=0;k--)
  	 ans=((ans<<1)+ok[j][k])%p;
    if (data[lower_bound(data,data+tot,ans)-data]==ans) printf("1\n");
    else  printf("0\n");
  }
}


这样用unsigned int 也是对的

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define  p  1000000007
using namespace std;
int n,m,a,b,tot;
char s[1003];
int mat[1003][1003],ok[1003][1003];
unsigned int sum[1000],ch[1003][1003],sh[1003][1003],data[1000003],maxn;
unsigned int mi,base;
int main()
{
 freopen("matrix.in","r",stdin);
 freopen("matrix.out","w",stdout);
 scanf("%d%d%d%d",&n,&m,&a,&b);
 for (int i=0;i<n;i++)
 {
  scanf("%s",s);
  for (int j=0;j<m;j++) mat[i][j]=s[j]-'0';
 }
 mi=1; base=1;
 for (int i=1;i<=b;i++)  mi=mi<<1; 
 for (int i=1;i<=a;i++)  base=base*mi;  
 for (int i=0;i<n;i++)
 {
  for (int j=1;j<=b;j++) 
    ch[i][m-b]=(ch[i][m-b]<<1)+mat[i][m-j];
  for (int j=m-b-1;j>=0;j--)
  {
   ch[i][j]=(ch[i][j+1]<<1)-mat[i][j+b]*mi+mat[i][j];
  }
 }
 for (int i=m-b;i>=0;i--)
 {
   for (int j=1;j<=a;j++)
    sh[n-a][i]=(sh[n-a][i]*mi)+ch[n-j][i];
   data[tot++]=sh[n-a][i];
   for (int j=n-a-1;j>=0;j--)
   {
    sh[j][i]=sh[j+1][i]*mi-ch[j+a][i]*base+ch[j][i];
    data[tot++]=sh[j][i];
   }
 }
  sort(data,data+tot);
  tot=unique(data,data+tot)-data;
  int t; scanf("%d",&t);
  for (int i=1;i<=t;i++)
  {
  	for (int j=0;j<a;j++)
  	{
  	 scanf("%s",s);
  	 for (int k=0;k<b;k++) ok[j][k]=s[k]-'0';
  	}
  	unsigned int ans=0;
  	for (int j=a-1;j>=0;j--)
  	for (int k=b-1;k>=0;k--)
  	 ans=(ans<<1)+ok[j][k];
    if (data[lower_bound(data,data+tot,ans)-data]==ans) printf("1\n");
    else  printf("0\n");
  }
}

另外我在编写时是按照学长所说的先把行按二进制处理,因为一共b列所以,那么转成的二进制最大不超过2^b,所以在把2^b直接当作字符基。也可以hash行的时候和把a行hash在一起的时候都用一个奇怪的大质数。



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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值