您的位置:首页 > 其它

bzoj 2462 [BeiJing2011]矩阵模板

2016-03-28 15:04 369 查看

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

Day4

题解:二维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在一起的时候都用一个奇怪的大质数。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: