给定两个矩阵,判断第二个矩阵在第一个矩阵哪些位置出现过
输出位置的左上角
有多个答案,按字典序输出
哈希字符串
ebacd
hash(ebacd) = (5B^4+2B^3+B^2+3B^1+4B^0) mod mo
B是大于26的任意整数,最好取质数 mo为一个较大的质数
将这个推广到正常的整数序列中去,假设所给的整数序列是a_i
h a s h ( { a i } ) = ( ∑ i = 1 n a i B n − i ) m o d m o hash(\{a_i\}) = (\sum_{i=1}^na_iB^{n-i}) \space\space mod \space mo hash({ ai})=(i=1∑naiBn−i) mod mo
其中B是任意大于max{a_i}的整数,标程中B取233
解决了一维,拓展到二维
计算一个矩形A_{nXm}的hash值,我们先对每一行求一次hash值,得到n个hash值,然后再对n个hash值再做一遍一维hash
一般来说下一次hash时,B取多少?应该大于等于mo
尽量减少碰撞的可能(密码学里有个碰撞的概念)
取两套B mo,如果算出来的hash1和hash2都相等,则认为这两个矩形相等
mo1 是1e9+7 mo2是1e9+9 B都是233(大于100(题目数据取值范围)的任意质数)
pair是一个二元组
for (int i = 1; i <= q; i++) {
p1 = p1 * pw % mo1;
p2 = p2 * pw % mo2;
}
得到B^q次方的值,中间需要对mo取模
p1 = (mo1 - p1) % mo1;
(mo1-p1) = (-p1+mo1) 因为最后要求的是(-B^q)的值
for (int i = 1; i <= n; i++) {
long t1 = 0, t2 = 0;
for (int j = 1; j <= m; j++) {
if (j < q) {
t1 = (t1 * pw + a[i][j]) % mo1;
t2 = (t2 * pw + a[i][j]) % mo2;
} else {
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;
}
}
}
j
而当j>=q时,此时除了新加的a(i)(j),还要减掉前一次计算的头值
按列计算hash值过程类似
最后输出答案时,由于存储hash值的位置在右下角,而题目要求输出左上角,所以结果就是(i-p+1,j-q+1)
static class Task {
// 类似于c++里的pair
class pii {
public int first;
public int second;
public pii() {
first = second = 0;
}
public pii(int first, int second) {
this.first = first;
this.second = second;
}
}
final int N = 1005;
final long mo1 = (long) 1e9 + 7; // 模数最好取质数
final long mo2 = (long) 1e9 + 9;
final long pw = 233; // base
// 全局变量
// bb:对于b数组,bb[0][i][j]表示从(i,1)到(i,j)的横向hash值(对mo1)取模,bb[1][i][j]表示从(i,1)到(i,j)的横向hash值(对mo2)取模
long[][][] h1 = new long[2][N][N];
long[][][] h2 = new long[2][N][N];
long[][][] bb = new long[2][N][N];
// 为了减少复制开销,我们直接读入信息到全局变量中
// a, b:题目所述数组,a的大小为(n+1)*(m+1),b的大小为(p+1)*(q+1),下标均从1开始有意义(下标0无意义,你可以直接无视)
// n, m, p, q:题中所述
// n,m,p,q都是实际大小,上面+1是因为数组下标从0开始
int[][] a = new int[N][N];
int[][] b = new int[N][N];
int n, m, p, q;
// 求出a中有哪些位置出现了b
// 返回值:一个pii的数组,包含所有出现的位置
List<pii> 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
// pw^q在后面要用
long p1 = 1, p2 = 1;
for (int i = 1; i <= q; i++) {
p1 = p1 * pw % mo1;
p2 = p2 * pw % mo2;
}
// p1 = ((0-p1)%mo1 + mo1) %mo1
// 因为p1在上面幂乘的过程中一直对mo1取模,所以是mo1之内的
// p1 = (mo1-p1) % mo1;
p1 = (mo1 - p1) % mo1;
p2 = (mo2 - p2) % mo2;
// 用a数组计算横向hash值,存储为h1[0]
// i表示矩阵的行号,是第几行
for (int i = 1; i <= n; i++) {
long t1 = 0, t2 = 0;
// j表示矩阵的列
for (int j = 1; j <= m; j++) {
// 一直循环到q-1
// 假设n,m=4,p,q=2
// 以第一行为例
// 此处算出[1][1]+[1][2]的hash值,存储到了[1][2]
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-1] pw + a[i][j]
// 所以 之前的t1乘上pw后,再加上a[i][j]就得到了之后的t1
t1 = (t1 * pw + a[i][j]) % mo1;
t2 = (t2 * pw + a[i][j]) % mo2;
}
// 怎么由[1][1]+[1][2]推出[1][2]+[1][3]呢
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^q)%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],与上方类似
// j表示矩阵的列
for (int j = 1; j <= m; j++) {
long t1 = 0, t2 = 0;
// i表示矩阵的行
for (int i = 1; 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数组,与上方类似
// 计算小矩阵的hash值
// 先按行计算一遍
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;
}
}
p1 = p2 = 0;
// 再按列计算一遍
// 用bb数组的最后一列来计算整个b数组的hash值,存储为p1
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)
List<pii> ans = new ArrayList<>();
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.add(new pii(i - p + 1, j - q + 1));
}
}
}
return ans;
}
}
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
s | % | # | a | # | a | # | b | # | a | # | a | # | $ |
len | 0 | 0 | 1 | 2 | 1 | 0 | 5 | 0 | 1 | 2 | 1 | 0 | 0 |
cur | 0 | 0 | 2 | 3 | 3 | 3 | 6 | 6 | 6 | 6 | 6 | 6 | 0 |
cursor是指针的意思
cur作为中心点
i是移动点
pos = cur*2-i 就是关于中心对称的右端点位置,
int pos = (cur<<1) - i;
int now = Math.max(Math.min(len[pos],cur+len[cur]-i),0);
cur+len(cur)-i求出i这个点通过对称性得出的至少的回文串长度
while(s[i-now-1] == s[i+now+1]) {
++now;
}
尝试往两边扩张
if (i+now > cur + len[cur]){
cur = i;
}
更新cur,因为之前i+l(i)总是小于等于cur+l(cur)
最后得到的答案就是最长回文串除以2向上取整,就是这个回文串及其子串的所有回文串数目
static class Task {
/* 全局变量 */
final int N = 500005;
char[] s = new char[N*2];
int[] len = new int[N*2];
// 计算str中有多少个回文子串
// 返回值:子串的数目
long getAnswer(String str) {
int n = str.length();
for (int i = n; i != 0 ; --i) {
s[i<<1] = str.charAt(i-1);
s[i<<1 | 1] = 0;
}
n = n << 1 | 1;
s[1] = 0;
s[0] = 1;
s[n+1] = 2;
// manacher算法
int cur = 1;
long ans = 0;
for (int i = 2; i <= n; i++) {
// cursor是对称中心
// i和pos是关于cursor对称的对称点
int pos = (cur<<1) - i;
// now是pos到0的距离和i到2n的距离的最小者,如果越界了就是0
// now就是i能往两侧至少能拓展多长
int now = Math.max(Math.min(len[pos],cur+len[cur]-i),0);
while(s[i-now-1] == s[i+now+1]) {
++now;
}
// cur+len[cur]要保证是最大的
// 找到一个比当前对称中心所处的回文串要大的对称中心点,更新对称中心
if (i+now > cur + len[cur]){
cur = i;
}
// 相当于now/2取上界
// 一个字母也算回文串
ans += Math.ceil(now/2.0);
// 更新目前的回文串长度
len[i] = now;
}
return ans;
}
}