矩阵hash 习题

描述
给定两个矩阵,判断第二个矩阵在第一个矩阵的哪些位置出现过。

输入
输入的第一行包含四个正整数a,b,c,d,表示第一个矩阵大小为a×b,第二个矩阵的大小为c×d。

接下来是一个a×b的矩阵。

再接下来是一个c×d的矩阵。

保证矩阵中每个数字都为正整数且不超过100。

输出
若第二个矩阵在第一个矩阵的(i,j)位置出现(即出现位置的左上角),输出i和j。若有多个位置,按字典序从小到大的顺序依次输出。

字典序:对于两个位置(a,b),(c,d),若a<c则(a,b)比(c,d)小,若a>c则(a,b)比(c,d)大,若a=c则再像前边一样比较b和d。

样例1输入
4 4 2 2
1 2 1 2
2 3 2 3
2 1 2 3
2 2 3 1
1 2
2 3
样例1输出
1 1
1 3
3 2
样例1解释
矩阵2在矩阵1的(1,1)、(1,3)、(3,2)这些位置出现了。

样例2
请查看下发文件内的sample2_input.txt和sample2_output.txt。

限制
对于50%的数据,a,b,c,d ≤ 50;

对于100%的数据,a,b,c,d ≤ 1000。

时间:4 sec

空间:512 MB

解释

每个矩阵先对行取一次hash然后再对列取一次hash。
取两次hash 防止冲突。在这里插入图片描述

#include <bits/stdc++.h>
using namespace std;

// ================= 代码实现开始 =================

const int N = 1005;

typedef long long ll;
typedef pair<int,int> pii;

const ll mo1 = 1e9 + 7; //模数最好都取质数
const ll mo2 = 1e9 + 9;
const ll pw = 233; //base 其要大于所有字符的种类树

//h1,h2:对于a数组 h1[0][i][j] 表示从(i,1)到(i,j)的横向hash值(s对mo1取模),h2[1][i][j]表示从(i-q+1,j)到(i,j)的纵向hash值(对mo2取模),其余的类似
//bb:对于b数组,bb[0][i][j]表示从(i,1)到(i,j)的横向hash值(对mo1)取模,bb[1][i][j]表示从(i,1)到(i,j)的横向hash值(对mo2)取模
ll h1[2][N][N], h2[2][N][N], bb[2][N][N];

// 为了减少复制开销,我们直接读入信息到全局变量中
// a, b:题目所述数组,a的大小为(n+1)*(m+1),b的大小为(p+1)*(q+1),下标均从1开始有意义(下标0无意义,你可以直接无视)
// n, m, p, q:题中所述
int a[N][N], b[N][N], n, m, p, q;

// 求出a中有哪些位置出现了b
// 返回值:一个pair<int, int>的数组,包含所有出现的位置
vector<pair<int, int>> getAnswer() {
    //(a+b)%mo = ((a%mo)+(b%mo)) % mo
    //(a-b)%mo = ((a-b)%mo+mo) % mo //要把范围限制在[0,mo-1)内
    //(a*b)%mo = ((a%mo)*(b%mo))%mo
    //注意,以下所有变量类似于p1,p2的,都表示同一意义,仅仅是取的模数不同(前者是对mo1取模,后者是对mo2),所以下方注释仅给mo1的解释
    
    //p1 = (-pw^q) %mo1
    ll p1 =1, p2=1;
    for(int i=1; i<=q; ++i){
        p1 = p1 * pw % mo1;
        p2 = p2 * pw % mo2;
    }
    p1 = (mo1-p1) % mo1;
    p2 = (mo2-p2) % mo2;
    
    //用a数组计算横向hash值,存储为h1[0]
    for (int i=1; i<=n; ++i){
        ll t1=0, t2=0;
        for(int j = 1; j<=m; ++j ){
            if(j<q){
                //之前的t1=a[i][0]pw^(j-2)+a[i][1]pw^(j-3)+...a[i][j-2]pw+a[i][j-1]
                //之后的t1=a[i][0]pw^(j-1)+a[i][1]pw^(j-2)+...a[i][j-2]pw+a[i][j]
                //所以”之前的t1“乘上pw后,再加上a[i][j]就得到了”之后的t1“
                t1 = (t1*pw+a[i][j])%mo1;
                t2 = (t2*pw+a[i][j])%mo2;
            }else{
                //之前的t1=a[i][j-q]pw^(j-1)+a[i][j-q+1]pw^(j-2)+...+a[i][j-2]pw+a[i][j-1]
                //之后的t1=a[i][j-q+1]pw^(j-1)+a[i][j-q+2]pw^(j-2)+...+a[i][j-1]pw+a[i][j]
                //所以”之前的t1“乘上pw后,再减去a[i][j-q]pw^j,再加上a[i][j]就得到了”之后的t1“
                t1 = h1[0][i][j] = (t1*pw+a[i][j]+p1*a[i][j-q]) % mo1;
                t2 = h2[0][i][j] = (t2*pw+a[i][j]+p2*a[i][j-q]) % mo2;
            }
        }
    }
  
    //p1 = (-pw^p) %mo1
    p1 =1, p2=1;
    for(int i=1; i<=p; ++i){
        p1 = p1 * pw % mo1;
        p2 = p2 * pw % mo2;
    }
    p1 = (mo1-p1) % mo1;
    p2 = (mo2-p2) % mo2;
    
    
    //用h1[0]数组计算纵向hash值,存储为h1[1],与上方类似
    for(int j=1; j<=m; ++j){
        ll t1=0,t2=0;
        for(int i=0; i<=n; ++i){
            if(i<p){
                t1 = (t1*pw+h1[0][i][j])%mo1;
                t2 = (t2*pw+h2[0][i][j])%mo2;
            }else{
                t1 = h1[1][i][j] = (t1*pw+h1[0][i][j]+p1*h1[0][i-p][j])%mo1;
                t2 = h2[1][i][j] = (t2*pw+h2[0][i][j]+p2*h2[0][i-p][j])%mo2;
            }
        }
    }
    
    //计算b数组的横向hash值,存储为bb数组,与上方类似
    for(int i=1; i<=p; ++i)
        for(int j=1; j<=q; ++j){
            bb[0][i][j]=(bb[0][i][j-1]*pw+b[i][j])%mo1;
            bb[1][i][j]=(bb[1][i][j-1]*pw+b[i][j])%mo2;
        }
    
    //用bb数组的最后一列h来计算整个b数组的hash值,存储为p1
    p1=p2=0;
    for(int i=1; i<=p; ++i){
        p1=(p1*pw+bb[0][i][q])%mo1;
        p2=(p2*pw+bb[1][i][q])%mo2;
    }
    
    //若值相同,说明匹配到了相同的矩形(右下角),题中要求输出左上角,故得到的坐标是(i-p+1,j-q+1)
    vector<pii> ans;
    for(int i=p; i<=n; ++i)
        for(int j=q;j<=m; ++j)
            if(h1[1][i][j] == p1 && h2[1][i][j]==p2)
                ans.push_back(pii(i-p+1,j-q+1));
    return ans;
}

// ================= 代码实现结束 =================

int main() {
    scanf("%d%d%d%d", &n, &m, &p, &q);
    for (int i = 1; i <= n; ++i)
        for (int j = 1; j <= m; ++j)
            scanf("%d", &a[i][j]);
    for (int i = 1; i <= p; ++i)
        for (int j = 1; j <= q; ++j)
            scanf("%d", &b[i][j]);
    vector<pair<int, int>> ans = getAnswer();
    for (int i = 0; i < int(ans.size()); ++i)
        printf("%d %d\n", ans[i].first, ans[i].second);
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值