[HDU4029]Distinct Sub-matrix/[JZOJ4683]矩阵

本文介绍了一种优化方法,用于高效计算给定矩阵中本质不同的子矩阵个数。通过结合哈希技巧与后缀自动机等数据结构,将原本的时间复杂度从O(n^4)降低到O(n^3log_2n^2)。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目大意

给定一个n×m的矩阵,每个位置有一个大写字母。请求出这个矩阵本质不同的子矩阵的个数(参考字符串本质不同的子串)。
1n,m110


题目分析

一个很naive的想法,暴力枚举,然后使用多哈希O(1)判断,时间复杂度O(n4)
然而这题我们可以使用一种更高的姿势水平玩哈希。枚举子矩阵宽度w,我们要统计宽度为w的矩阵对答案的贡献,显然一个宽度为w的矩阵可以有多个宽度为w高度为1的矩阵上下拼接而成。我们将所有宽度为w高度为1的子矩阵用哈希压成数值,就可以去掉考虑一维。当然不同列开头的不能上下连接,于是我们在列与列之间要加上特殊数值。
问题变为求这个数组的本质不同的连续子序列个数,这是后缀数组和后缀自动机的经典问题。当然这题后缀自动机要使用map来存边,时间复杂度和后缀数组一样都是O(n3log2n2)的。
可能有点难理解,可以参考一下我的代码。


代码实现

#include <algorithm>
#include <iostream>
#include <cstdio>
#include <cctype>

using namespace std;

typedef pair<int,int> P;

#define mkp(a,b) make_pair(a,b)
#define ft first
#define sd second

const int MOD=1004535809;
const int P0=3873383;
const int P1=9598609;
const int N=120;
const int M=120;
const int S=N*M;

int Ws[S],Wv[S],x[S],y[S],SA[S],rank[S],height[S];
int n,m,cnt,len;
int pw[2][M],rp[2][M];
int hash[2][N][M];
char s[N][M];
P a[S];

int quick_power(int x,int y)
{
    int ret=1;
    for (;y;y>>=1,x=1ll*x*x%MOD) if (y&1) ret=1ll*ret*x%MOD;
    return ret;
}

void pre()
{
    pw[0][0]=1;
    for (int i=1;i<=m;i++) pw[0][i]=1ll*pw[0][i-1]*P0%MOD;
    rp[0][m]=quick_power(pw[0][m],MOD-2);
    for (int i=m-1;i>=0;i--) rp[0][i]=1ll*rp[0][i+1]*P0%MOD;
    for (int i=0;i<n;i++)
    {
        hash[0][i][0]=s[i][0];
        for (int j=1;j<m;j++) hash[0][i][j]=(hash[0][i][j-1]+1ll*pw[0][j]*s[i][j])%MOD;
    }
    pw[1][0]=1;
    for (int i=1;i<=m;i++) pw[1][i]=1ll*pw[1][i-1]*P1%MOD;
    rp[1][m]=quick_power(pw[1][m],MOD-2);
    for (int i=m-1;i>=0;i--) rp[1][i]=1ll*rp[1][i+1]*P1%MOD;
    for (int i=0;i<n;i++)
    {
        hash[1][i][0]=s[i][0];
        for (int j=1;j<m;j++) hash[1][i][j]=(hash[1][i][j-1]+1ll*pw[1][j]*s[i][j])%MOD;
    }
}

void build(int w)
{
    len=0;
    for (int j=0;j<m-w+1;j++)
    {
        for (int i=0;i<n;i++)
            a[len++]=mkp(1ll*(hash[0][i][j+w-1]-(j?hash[0][i][j-1]:0)+MOD)%MOD*rp[0][j]%MOD,1ll*(hash[1][i][j+w-1]-(j?hash[1][i][j-1]:0)+MOD)%MOD*rp[1][j]%MOD);
        a[len++]=mkp(-(j+1),-(j+1));
    }
}

bool cmp(int *r,int st1,int st2,int l){return st1+l>=len||st2+l>=len||r[st1]!=r[st2]||r[st1+l]!=r[st2+l];}

bool compare(int x,int y){return a[x]<a[y];}

void DA()
{
    int i,p,l,mx;
    for (i=0;i<len;i++) SA[i]=i;
    sort(SA,SA+len,compare);
    for (x[SA[0]]=p=0,i=1;i<len;i++) x[SA[i]]=(p+=a[SA[i]]!=a[SA[i-1]]);
    for (l=1;l<=len&&p!=len-1;l<<=1)
    {
        for (p=0,i=len-l;i<len;i++) y[p++]=i;
        for (i=0;i<len;i++) if (SA[i]>=l) y[p++]=SA[i]-l;
        for (i=0,mx=0;i<len;i++) mx=max(mx,Wv[i]=x[y[i]]);
        for (i=0;i<=mx;i++) Ws[i]=0;
        for (i=0;i<len;i++) Ws[Wv[i]]++;
        for (i=1;i<=mx;i++) Ws[i]+=Ws[i-1];
        for (i=len-1;i>=0;i--) SA[--Ws[Wv[i]]]=y[i];
        for (i=0;i<len;i++) y[i]=x[i],x[i]=0;
        for (x[SA[0]]=p=0,i=1;i<len;i++) x[SA[i]]=(p+=cmp(y,SA[i],SA[i-1],l));
    }
    for (i=0;i<len;i++) rank[SA[i]]=i;
}

void getheight()
{
    for (int k=0,i=0,j;i<len;i++)
    {
        k?--k:k;
        if (!rank[i]) continue;
        for (j=SA[rank[i]-1];j+k<len&&i+k<len&&a[j+k]==a[i+k];) k++;
        height[rank[i]]=k;
    }
}

void count()
{
    for (int i=0;i<len;i++)
        if (a[SA[i]].ft>=0) cnt+=((SA[i]+1)/(n+1)*(n+1)+n-SA[i]-height[i]);
}

int main()
{
    freopen("matrix.in","r",stdin),freopen("matrix.out","w",stdout);
    scanf("%d%d",&n,&m);
    for (int i=0;i<n;i++) scanf("%s",s[i]);
    pre();
    for (int w=1;w<=m;w++) build(w),DA(),getheight(),count();
    printf("%d\n",cnt);
    fclose(stdin),fclose(stdout);
    return  0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值