【UVA 11019 Matrix Matcher 】 二维Hash+尺取 / 二维AC自动机

8 篇文章 0 订阅
4 篇文章 0 订阅

UVA-11019
本体题意就是给你AB两个字符矩阵,问你B矩阵在A矩阵中的出现次数。

Hash解法

我们可以进行二维hash,其实就是把n个横向串连在一起hash。
注意判相等的时候,我们不断进行尺取+hash,尺取的过程,我们删除当前第一行的hash值加上最后一行的hash值,删除第一行的hash值直接删去就可以
例如
AAA A A A
BBB B B B
CCC C C C
我们删去第一行的hash值 相当于把矩阵变成了
000 000
BBB B B B
CCC C C C
此时我们再添加最后一行
000 000
BBB B B B
CCC C C C
DDD D D D
如果这时候的B矩阵是
BBB B B B
CCC C C C
DDD D D D
这两个矩阵的hash值不同的,为了处理这种情况,我们把B矩阵相应的添加前几行
变成
000 000
BBB B B B
CCC C C C
DDD D D D
这样再去匹配就可以了。
以上就是二维hash大概的处理方法(是我自己想的做法,如果有其他好的尺取方法欢迎指教
掌握了这个做法,我们就可以枚举矩阵的左上角,然后对于当前列数的矩阵从上向下进行尺取,hash判断就可以了。
UVA-11019代码

#include<stdio.h>
#include<algorithm>
#include<iostream>
using namespace std;
const int maxn = 1e3+5;
const int MAXN = 1e6+5;
typedef unsigned long long ull;
ull hash_[maxn][maxn],xp[MAXN];
char str[maxn][maxn];
char str2[maxn][maxn];
void init()
{
    xp[0]=1;
    for(int i=1;i<MAXN;i++)
    {
        xp[i]=xp[i-1]*13331;
    }
    return ;
}
ull Get_hash(int i,int j,int l)
{
    return hash_[i][j]-hash_[i][j+l]*xp[l];
}
int main()
{
    init();
    int t;
    scanf("%d",&t);
    while(t--)
    {
        int ans=0;
        int n,m,x,y;
        scanf("%d%d",&n,&m);
        for(int i=0;i<n;i++)
        {
            scanf("%s",str[i]);
        }
        for(int i=0;i<n;i++)
        {
            hash_[i][m]=0;
            for(int j=m-1;j>=0;j--)
            {
                hash_[i][j]=hash_[i][j+1]*13331+str[i][j]-'a'+1;//每一行分别处理hash值
            }
        }
        scanf("%d%d",&x,&y);
        for(int i=0;i<x;i++)
            scanf("%s",str2[i]);
        ull tmp=0;
        for(int i=x-1;i>=0;i--)
        {
            for(int j=y-1;j>=0;j--)
            {
                tmp=tmp*13331+str2[i][j]-'a'+1;//处理出匹配矩阵的hash值
            }
        }
        for(int i=0;i<=m-y;i++)//枚举横坐标起点
        {
            ull tt=tmp;
            ull tmp2=0;
            for(int j=x-1;j>=0;j--)
            {
                tmp2=tmp2*xp[y]+Get_hash(j,i,y);
            }
             if(tt==tmp2) ans++;
             for(int j=x;j<n;j++)
             {
                 tmp2-=Get_hash(j-x,i,y)*xp[(j-x)*y];//尺取过程去除第一行,也就是将第一行变为0
                 tmp2+=Get_hash(j,i,y)*xp[j*y];//加上最后一行
                 tt=tt*xp[y];//将匹配矩阵第一行添上0
                 if(tmp2==tt) ans++;
             }
        }
        printf("%d\n",ans);
    }
    return 0;
}

AC自动机解法

我们用B矩阵的每一行插入Trie树,用A的每一行去跑AC自动机,得到A的每个位置可以匹配B的哪些行,存到一个三维矩阵con[i][j][k]里,con[i][j][k]表示A矩阵第i行第j列的点可匹配B矩阵中的第k行,之后枚举A矩阵起点,然后向下扫x行,检验是否都可以匹配,都可以匹配就更新答案。
注意B中可能存在某些相同行,所以用一个vector进行存储

#include<stdio.h>
#include<algorithm>
#include<iostream>
#include<string.h>
#include<queue>
#include<stdlib.h>
using namespace std;
const int maxn = 5e5+5;
int strl[1005];
int n,m,x,y;
bool con[1005][1005][105];
struct ACTrie
{
    int tree[maxn][26],fail[maxn],end_[maxn];
    int root,cnt;
    vector<int> v[maxn];
    int newnode()
    {
        for(int i=0;i<26;i++)
            tree[cnt][i]=-1;
        end_[cnt++]=0;
        return cnt-1;
    }
    void init()
    {
        cnt=0;
        root=newnode();
    }
    void insert_(char str[],int xpos)
    {
        int len= strlen(str);
        int pos=root;
        for(int i=0;i<len;i++)
        {
            int id=str[i]-'a';
            if(tree[pos][id]==-1)
                tree[pos][id]=newnode();
            pos=tree[pos][id];
        }
        if(end_[pos]==0)
        {
            v[pos].clear();
            end_[pos]=1;
        }
        v[pos].push_back(xpos);//可能B中某些行相同,所以存到一起
    }
    void build()
    {
        queue<int> que;
        fail[root]=root;
        for(int i=0;i<26;i++)
        {
            if(tree[root][i]==-1) tree[root][i]=root;
            else
            {
                fail[tree[root][i]]=root;
                que.push(tree[root][i]);
            }
        }
        while(!que.empty())
        {
            int now=que.front();
            que.pop();
            for(int i=0;i<26;i++)
            {
                if(tree[now][i]==-1)
                    tree[now][i]=tree[fail[now]][i];
                else
                {
                    fail[tree[now][i]]=tree[fail[now]][i];
                    que.push(tree[now][i]);
                }
            }
        }
    }
    void query(char str[],int xpos)
    {
        int len=strlen(str);
        int now=root;
        for(int i=0;i<len;i++)
        {
            now=tree[now][str[i]-'a'];//在Trie上不断向后跑
            int temp=now;
            while(temp!=root)
            {
                if(end_[temp])
                {
                    for(int j=0;j<v[temp].size();j++)
                    {
                        int pp=v[temp][j];
                        con[xpos][i-y+1][pp]=true;//列的起始位置是i-y+1
                    }
                }
                temp=fail[temp];
            }
        }
        return ;
    }
};
char str[maxn*2];
char str1[1005][1005];
ACTrie ac;
int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d",&n,&m);
        for(int i=0;i<n;i++) scanf("%s",str1[i]);
        ac.init();
        scanf("%d%d",&x,&y);
        for(int i=0;i<x;i++)
        {
            scanf("%s",str);
            ac.insert_(str,i);
        }
        ac.build();
        for(int i=0;i<n;i++)
        {
            for(int j=0;j<m;j++)
            {
                for(int k=0;k<x;k++)
                {
                    con[i][j][k]=false;
                }
            }
        }
        for(int i=0;i<n;i++)
        {
            ac.query(str1[i],i);
        }
        int ans=0;
        for(int i=0;i<=n-x;i++)//注意边界
        {
            for(int j=0;j<=m-y;j++)//注意边界
            {
                int flag=0;
                for(int k=i,l=0;k<i+x;k++,l++)
                {
                    if(!con[k][j][l])
                    {
                        flag=1;
                        break;
                    }
                }
                if(flag==0) ans++;//全都匹配
            }
        }
        printf("%d\n",ans);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值