例题3.16 矩阵匹配器 UVa11019

1.题目描述:点击打开链接

2.解题思路:本题可以利用AC自动机解决,但是发现,这种方法时间效率比较低,个人推荐利用二维Hash来解决本题。经过OJ上测试,AC自动机的方法需要1s以上,而二维Hash只需要不到100ms!因此下面介绍如何用二维hash来解决本题。

首先,任何hash技术都需要给定一个函数,使得不同字符串经过计算得到的hash值产生的冲突越少越好。对于字符矩阵,我们一般利用二维hash来处理。虽然是二维,但原理和一维的类似,即首先把每一行的前j个字符进行一维hash,对前i行进行处理,得到i行1列的hash值,接下来对这i行1列hash值再次hash,最终得到的hash值就是(i,j)处的hash值。写成递推式如下:

上式中,每一行进行hash时候,seed=q;每一列进行hash的时候,seed=p,下面举一个简单的例子来说明此方法。

假设现在的字符矩阵如下:

对该矩阵按照上述公式计算每一格的hash值,可以得到下面的hash矩阵:

通过观察不难发现,二维hash的确就是2个一维hash的复合过程。而且,如果p,q选择合适,可以保证(i,j)处的hash值就可以看做子矩阵(0,0)~(i,j)的hash值,而且只要子矩阵不相同,那么对应的hash值一定不同。下面我们就用矩阵右下角的hash值代表整个矩阵的hash值。

那么,如何从该矩阵中计算(i,j)~(i+x-1,j+y-1)这个子矩阵的hash值呢?还是利用最初的计算公式,只需要反复迭代使用,即可得到下述公式:

这样,有了上述的分析,本题就不难解决了,首先计算出原始字符矩阵的hash矩阵,同时算出输入的P矩阵的hash值h,通过枚举x*y的子矩阵,并计算该子矩阵的hash值是否等于h,如果是,则ans++。最终即可得到答案。

本题的时间复杂度是O((N-x)*(M-y))。

3.代码:

#include<iostream>
#include<algorithm>
#include<cassert>
#include<string>
#include<sstream>
#include<set>
#include<bitset>
#include<vector>
#include<stack>
#include<map>
#include<queue>
#include<deque>
#include<cstdlib>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<ctime>
#include<cctype>
#include<functional>
#pragma comment(linker, "/STACK:1024000000,1024000000")
using namespace std;

#define me(s)  memset(s,0,sizeof(s))
#define rep(i,n) for(int i=0;i<(n);i++)
typedef long long ll;
typedef unsigned int uint;
typedef unsigned long long ull;
//typedef pair <int, int> P;

const int N=1101;
const int p=131;
const int q=1331;

ll Hash[N][N];
ll hv[110][110];
ll powp[N],powq[N];
char str[N];

void init()//初始化p^i,q^i的结果
{
    powp[0]=powq[0]=1;
    for(int i=1;i<N;i++)
    {
        powp[i]=powp[i-1]*p;
        powq[i]=powq[i-1]*q;
    }
}

int main()
{
    int T;
    scanf("%d",&T);
    init();
    while(T--)
    {
        int n,m;
        scanf("%d%d",&n,&m);
        me(Hash);me(hv);
        for(int i=1;i<=n;i++)
        {
            scanf("%s",str+1);
            for(int j=1;j<=m;j++)
                Hash[i][j]=Hash[i-1][j]*p+Hash[i][j-1]*q-Hash[i-1][j-1]*p*q+str[j];//计算Hash矩阵
        }
        int x,y;
        scanf("%d%d",&x,&y);
        for(int i=1;i<=x;i++)
        {
            scanf("%s",str+1);
            for(int j=1;j<=y;j++)
                hv[i][j]=hv[i-1][j]*p+hv[i][j-1]*q-hv[i-1][j-1]*p*q+str[j];//计算P矩阵的hash值
        }
        ll h;
        int ans=0;
        for(int i=1;i<=n-x+1;i++)
            for(int j=1;j<=m-y+1;j++)//枚举x*y的子矩阵
        {
            h=Hash[i+x-1][j+y-1]-Hash[i-1][j+y-1]*powp[x]-Hash[i+x-1][j-1]*powq[y]+Hash[i-1][j-1]*powp[x]*powq[y];
            if(h==hv[x][y])ans++; //相等,则ans++
        }
        printf("%d\n",ans);
    }
}

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-10-20 14:30:01

例题3.16 矩阵匹配器 UVa11019的相关文章

Rust 1.7.0 匹配器 match 的简介和使用

使用过正則表達式的人应该都知道 matcher ,通过 matcher 匹配器运算正則表達式,完毕一系列的匹配规则. 在Rust 中 没有 switch 语句.matcher 就是 switch 的一个变形,但比其它语言中的 switch 更强大! 一.简单举例说明 简单的 matcher 和 if 语句很相似,假设是简单的条件推断能够用if语句: let n = 5; if n < 0 { print!("{} is negative", n); } else if n >

saltstack 自动化运维神器(三)节点组及复合匹配器

saltstack实现远程配置管理功能首先是要先匹配到对应的target minion,然后才会将命令发送到匹配到的minion上去执行.这里介绍两种比较强大的匹配方法,一是创建节点组:二是使用复合匹配器. 节点组将不同的主机分配到不同的组中去,便于实现主机的集中化管理,接下来首先看salt分组功能的实现. 要使用salt的分组功能,需要在master节点上进行配置,配置的方式有两种: (1).将分组的信息写在master的主配置文件 (2).将分组的信息写在一个单独的配置文件中,然后主配置文件

[Google Guava]字符串处理:连接器、拆分器、字符匹配器

一.连接器[Joiner] 二.拆分器[Splitter] 三.字符匹配器[CharMatcher] 四.字符集[Charsets] Charsets:针对所有Java平台都要保证支持的六种字符集提供了常量引用.尝试使用这些常量,而不是通过名称获取字符集实例. try { byte[] bytes = test.getBytes("UTf-8"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } 修改成

自动化运维神器之saltstack (三)节点组及复合匹配器

saltstack实现远程配置管理功能首先是要先匹配到对应的target minion,然后才会将命令发送到匹配到的minion上去执行.这里介绍两种比较强大的匹配方法,一是创建节点组:二是使用复合匹配器. 节点组将不同的主机分配到不同的组中去,便于实现主机的集中化管理,接下来首先看salt分组功能的实现. 看下环境先: hadoop0.updb.com    192.168.0.100    OS:CentOS 6.5        Role:master uadoop1.updb.com  

OpenCV之特征检测器(Feature Detector),描述子提取器(Descriptor Extractor)和描述子匹配器(Descriptor Matcher)

1.特征检测子 -Harris cv::cornerHarris(image,strength,3,3,0.01); -Fast cv::Ptr<cv::FastFeatureDetector> fast = cv::FastFeatureDetector::create(); //或 cv::FAST(InputArray image, std::vector<KeyPoint> &keypoints, int threshold) //或 cv::FAST(InputA

前端测试框架Jest系列教程 -- 匹配器

写在前面: 匹配器(Matchers)是Jest中非常重要的一个概念,它可以提供很多种方式来让你去验证你所测试的返回值,本文重点介绍几种常用的Matcher,其他的可以通过官网api文档查看. 常用的匹配方式: 第一种:相等匹配,这是我们最常用的匹配规则 test('two plus two is four', () => { expect(2 + 2).toBe(4); }); 在这段代码中 expact(2 + 2) 将返回我们期望的结果,通常情况下我们只需要调用expect就可以,括号中的

前端测试框架Jest系列教程 -- Matchers(匹配器)

写在前面: 匹配器(Matchers)是Jest中非常重要的一个概念,它可以提供很多种方式来让你去验证你所测试的返回值,本文重点介绍几种常用的Matcher,其他的可以通过官网api文档查看. 常用的匹配方式: 第一种:相等匹配,这是我们最常用的匹配规则 test('two plus two is four', () => { expect(2 + 2).toBe(4); }); 在这段代码中 expact(2 + 2) 将返回我们期望的结果,通常情况下我们只需要调用expect就可以,括号中的

shiro+密码匹配器验证登陆

1.先上工具类MD5Util   目前真正用到的是直接MD5加密的方法   未使用自定义加工密码加密 package cn.cjq.util; import cn.cjq.entity.User; import java.security.MessageDigest;import java.util.Random; public class MD5Util { /** * 加工密码,和登录一致. * @param user * @return */ public static User md5P

关于 Shiro 的权限匹配器和过滤器

项目源码:https://github.com/weimingge14/Shiro-project演示地址:http://liweiblog.duapp.com/Shiro-project/login 上一节,我们实现了自定义的 Realm,方式是继承 AuthorizingRealm这个抽象类,分别实现认证的方法和授权的方法. 这一节实现的代码的执行顺序: 1.Shiro定义的过滤器和自定义的过滤器,在自定义的过滤器中执行 Subject对象的判断是否具有某项权限的方法 isPermitted