CodeForces Gym题目页面传送门

有\(1\)个\(n1\times m1\)的字符矩阵\(a\)和\(1\)个\(n2\times m2\)的字符矩阵\(b\),求\(a,b\)的最大公共子矩阵。输出这个最大公共子矩阵的行数、列数和左上角分别在\(a,b\)中的坐标。若无解,输出\(\texttt{0 0}\)。若有多解,输出任意一组。

\(n1,m1,n2,m2\in[1,40]\)。

如果你还不知道Gym是什么,please点击这个

(以下假设\(n1,m1,n2,m2\)同阶,在复杂度里用\(n\)表示)

这是一个二维的字符串匹配问题。而我们只熟悉一维的字符串匹配算法,所以不妨先将\(2\)个字符矩阵一行行横着一维处理,然后再将横着一维处理所得的结果竖着一维合并。

先是横着一维处理。考虑求出\(lcP\)四维数组,其中\(lcP_{i,j,k,o}\)表示从\(a\)中的\((i,j)\)、\(b\)中的\((k,o)\)开始最多能向右延申多少个字符使得覆盖的字符串相等,即\(lcP_{i,j,k,o}=\max\limits_{a_{i,j\sim j+p-1}=b_{k,o\sim o+p-1}}\{p\}\)。这个用Z算法就可以轻松解决。显然\(lcP_{i,j,k,o}=z_{a_{i,j\sim m1}+\texttt{!}+b_{k},m1-j+2+o}\)。这样只需要对\(\forall i\in[1,n1],\forall j\in[1,m1],\forall k\in[1,n2],a_{i,j\sim m1}+\texttt{!}+b_{k}\)这\(\mathrm O\left(n^3\right)\)个长度为\(\mathrm O(n)\)的字符串跑Z算法即可,时间复杂度\(\mathrm O\left(n^4\right)\)。

然后考虑如何竖着一维合并。考虑枚举子矩阵在\(a\)中的坐标\((x1,y1)\)、在\(b\)中的坐标\((x2,y2)\),再枚举子矩阵的行数\(n'\),给它乘上能使得子矩阵在\(a,b\)中相等的最大列数\(m'\)并更新答案。显然,要满足子矩阵在\(a,b\)中匹配,就要满足在每行都匹配,即\(\forall i\in\left[1,n'\right],m'\leq lcP_{x1+i-1,y1,x2+i-1,y2}\)。那么\(m'_{\max}=\min\limits_{i=1}^{n'}\left\{lcP_{x1+i-1,y1,x2+i-1,y2}\right\}\)。这样在\(x1,y1,x2,y2\)固定时可以递推求出\(m'_{\max}\),时间复杂度\(\operatorname O\left(n^5\right)\)。

\(\mathrm O\left(n^5\right)\)有可能能过得去,但我是追求完美的OIer,当然不满足于这样卡着时限的复杂度。考虑将一些情况一并处理。不难发现在\(x1,y1,x2,y2\)固定时,\(a\)中的每个字符都唯一对应\(b\)中的\(1\)个字符(有可能越界),而对于\(2\)对左上角\(((x1_1,y1_1),(x2_1,y2_1)),((x1_2,y1_2),(x2_2,y2_2))\),若\(y1_1=y1_2,y2_1=y2_2,x1_1-x2_1=x1_2-x2_2\),则它们的字符对应情况是相等的,我们就可以把它们一起处理。这样,将可以一起处理的左上角对分到一组,就会有\(\mathrm O\left(n^3\right)\)组,对每一组分别处理。

由于每一组中左上角所在列固定,即对于每一行,从第几个字符开始匹配是固定的,那么对于每一组的处理可以看作这样一个问题:给定一个数列\(s\),求\(\max\limits_{i=1}^{|s|}\left\{\max\limits_{j=i}^{|s|}\left\{(j-i+1)\min\limits_{k=i}^j\{s_k\}\right\}\right\}\),其中\(s_i=lcP_{i,y1,i+x2-x1,y2}\)。考虑到\(\min\limits_{k=i}^j\{s_k\}\)一定等于\(s_{i\sim j}\)中的某一个元素,不妨枚举这个元素\(s_i\),求出它最多能往左/右延申到哪里,使得它保持最小值的身份,即设往左、右分别最多能能延申到\(up_i,dwn_i\),那么\(\forall j\in[up_i,dwn_i],s_j\geq s_i\),此时用\((dwn_i-up_i+1)s_i\)更新答案。

现在讨论怎么求\(up,dwn\)。以\(up\)为例,假设\(j<k\)且\(s_j\geq s_k\),如果\(s_k<s_i\),那么阻止\(i\)往左延申的那个元素肯定在\(k\)处或右边,不可能是\(j\);否则\(s_k\geq s_i\),结合\(s_j\geq s_k\)可以得到\(s_j\geq s_i\),\(j\)也不可能阻止\(i\)往左延申。根据这个性质,我们可以维护一个从底到顶严格单调递增的单调栈,阻止\(i\)往左延申的元素只可能在栈内。维护单调栈显然是\(\mathrm O(|s|)\)的。\(\forall i\in[1,|s|]\),可以在单调栈内二分找到阻止\(i\)往左延申的元素,这样一组中求\(up\)的时间复杂度为\(\mathrm O(|s|\log_2|s|)\)。然而其实根本不用二分,因为我们要找的是栈中最上面的\(j\)使得\(s_j<s_i\),而在将\(i\)入栈之前维护栈顶单调性时将所有栈顶的满足\(s_j\ge s_i\)的\(j\)弹出了,剩下来的栈顶就是我们要找的了(如果栈为空,则没有可以阻止\(i\)往左延申的元素,可以一直延伸到最左边)。这样每组求\(up\)复杂度\(\mathrm O(|s|)\)。\(dwn\)类似,所以每组总复杂度\(\mathrm O(|s|)=\mathrm O(n)\)。

一共\(\mathrm O\left(n^3\right)\)组,每组\(\mathrm O(n)\),总复杂度\(\mathrm O\left(n^4\right)\)。我们要写复杂度正确的算法,不能像窝女朋友libra9z一样写个\(\mathrm O\left(n^6\right)\)算法加剪枝水过去。。。

下面贴代码:(记得文件输入输出)

#include<bits/stdc++.h>
using namespace std;
#define mp make_pair
#define X first
#define Y second
#define pb push_back
const int N=40;
int n1,m1,n2,m2;//2个字符矩阵大小
char a[N+1][N+5],b[N+1][N+5];//2个字符矩阵
int s;//c的长度
char c[2*N+6];//临时字符串
void con(int x,int y,int z){//令c=a[x][y~m1]+'!'+b[z]
    s=0;
    for(int i=y;i<=m1;i++)c[++s]=a[x][i];
    c[++s]='!';
    for(int i=1;i<=m2;i++)c[++s]=b[z][i];
}
int z[2*N+6];//c的z数组
void z_init(){//对c跑Z算法
    int zl=0,zr=0;
    z[1]=s;
    for(int i=2;i<=s;i++)
        if(zr<i){
            z[i]=0;
            while(i+z[i]<=s&&c[i+z[i]]==c[1+z[i]])z[i]++;
            if(z[i])zl=i,zr=i+z[i]-1;
        }
        else if(i+z[i-zl+1]<=zr)z[i]=z[i-zl+1];
        else{
            z[i]=zr-i+1;
            while(i+z[i]<=s&&c[i+z[i]]==c[1+z[i]])z[i]++;
            zl=i;zr=i+z[i]-1;
        }
}
int lcP[N+1][N+1][N+1][N+1];//lcP[i][j][k][o]表示从a中的(i,j)、b中的(k,o)开始最多能向右延申多少个字符使得覆盖的字符串相等
int up[N],dwn[N];//从i开始最多能往左、右延伸到up[i],dwn[i]
int stk[N],top;//单调栈
int main(){
    freopen("money.in","r",stdin);freopen("money.out","w",stdout);
    cin>>n1>>m1;
    for(int i=1;i<=n1;i++)cin>>a[i]+1;
    cin>>n2>>m2;
    for(int i=1;i<=n2;i++)cin>>b[i]+1;
    for(int i=1;i<=n1;i++)for(int j=1;j<=m1;j++)for(int k=1;k<=n2;k++){//求lcP数组
        con(i,j,k);
        z_init();
        for(int o=1;o<=m2;o++)lcP[i][j][k][o]=z[m1-j+1+1+o];
    }
//  for(int i=1;i<=n1;i++)for(int j=1;j<=m1;j++)for(int k=1;k<=n2;k++)for(int o=1;o<=m2;o++)
//      printf("lcP[(%d,%d)][(%d,%d)]=%d\n",i,j,k,o,lcP[i][j][k][o]);
    pair<int,pair<pair<int,int>,pair<pair<int,int>,pair<int,int> > > > ans;//答案(以大小为关键字比较)
    ans.X=0;
    for(int i=1;i<=m1;i++)/*y1*/for(int j=1;j<=m2;j++)/*y2*/for(int k=-min(n1,n2)+1;k<min(n1,n2);k++)/*x2-x1*/{//枚举每组
//      printf("i=%d j=%d k=%d:\n",i,j,k);
        vector<pair<int,int> > v;//数列s,越界不计入
        for(int o=1;o<=n1;o++)if(1<=o+k&&o+k<=n2)/*判越界*/v.pb(mp(o,lcP[o][i][o+k][j]));
        //求up
        top=0;//清空单调栈
        for(int o=0;o<v.size();o++){
            while(top&&v[stk[top-1]].Y>=v[o].Y)top--;//维护栈顶单调性
            up[o]=top?stk[top-1]+1:0;//栈顶为阻止o向左延伸的元素
            stk[top++]=o;//将o入队
        }
        //求dwn
        top=0;//清空单调栈
        for(int o=v.size()-1;~o;o--){
            while(top&&v[stk[top-1]].Y>=v[o].Y)top--;//维护栈顶单调性
            dwn[o]=top?stk[top-1]-1:v.size()-1;//栈顶为阻止o向右延伸的元素
            stk[top++]=o;//将o入队
        }
//      for(int o=0;o<v.size();o++)printf("\tup[%d]=%d dwn[%d]=%d\n",o,up[o],o,dwn[o]);
        for(int o=0;o<v.size();o++){//枚举最小值
            int u=up[o],d=dwn[o];//左右边界
            ans=max(ans,mp((d-u+1)*v[o].Y,mp(mp(d-u+1,v[o].Y),mp(mp(v[u].X,i),mp(v[u].X+k,j)))));//更新答案
        }
    }
//  cout<<ans.X<<"\n";
    if(ans.X==0)puts("0 0");
    else printf("%d %d\n%d %d\n%d %d",ans.Y.X.X,ans.Y.X.Y,ans.Y.Y.X.X,ans.Y.Y.X.Y,ans.Y.Y.Y.X,ans.Y.Y.Y.Y);
    return 0;
}

CodeForces Gym 100213F Counterfeit Money的更多相关文章

  1. Codeforces Gym 101252D&&floyd判圈算法学习笔记

    一句话题意:x0=1,xi+1=(Axi+xi%B)%C,如果x序列中存在最早的两个相同的元素,输出第二次出现的位置,若在2e7内无解则输出-1. 题解:都不到100天就AFO了才来学这floyd判圈 ...

  2. Codeforces Gym 101190M Mole Tunnels - 费用流

    题目传送门 传送门 题目大意 $m$只鼹鼠有$n$个巢穴,$n - 1$条长度为$1$的通道将它们连通且第$i(i > 1)$个巢穴与第$\left\lfloor \frac{i}{2}\rig ...

  3. Codeforces Gym 101623A - 动态规划

    题目传送门 传送门 题目大意 给定一个长度为$n$的序列,要求划分成最少的段数,然后将这些段排序使得新序列单调不减. 考虑将相邻的相等的数缩成一个数. 假设没有分成了$n$段,考虑最少能够减少多少划分 ...

  4. 【Codeforces Gym 100725K】Key Insertion

    Codeforces Gym 100725K 题意:给定一个初始全0的序列,然后给\(n\)个查询,每一次调用\(Insert(L_i,i)\),其中\(Insert(L,K)\)表示在第L位插入K, ...

  5. Codeforces gym 101343 J.Husam and the Broken Present 2【状压dp】

     2017 JUST Programming Contest 2.0 题目链接:Codeforces gym 101343 J.Husam and the Broken Present 2 J. Hu ...

  6. codeforces gym 100553I

    codeforces gym 100553I solution 令a[i]表示位置i的船的编号 研究可以发现,应是从中间开始,往两边跳.... 于是就是一个点往两边的最长下降子序列之和减一 魔改树状数 ...

  7. Codeforces GYM 100876 J - Buying roads 题解

    Codeforces GYM 100876 J - Buying roads 题解 才不是因为有了图床来测试一下呢,哼( 题意 给你\(N\)个点,\(M\)条带权边的无向图,选出\(K\)条边,使得 ...

  8. codeforces Gym 100187J J. Deck Shuffling dfs

    J. Deck Shuffling Time Limit: 2   Sec Memory Limit: 256 MB 题目连接 http://codeforces.com/gym/100187/pro ...

  9. Codeforces Gym 100187K K. Perpetuum Mobile 构造

    K. Perpetuum Mobile Time Limit: 2 Sec Memory Limit: 256 MB 题目连接 http://codeforces.com/gym/100187/pro ...

随机推荐

  1. oracle建数据库

    oracle用户界面登陆 用户要切换到oracle sqlplus / as sysdba //sys用户是oracle的最高管理员所以要加上as help index //查看命令列表,sql中不区 ...

  2. 数据预处理 | 通过 Z-Score 方法判断异常值

    判断异常值方法:Z-Score 计算公式 Z = (X-μ)/σ 其中μ为总体平均值,X-μ为离均差,σ表示标准差.z的绝对值表示在标准差范围内的原始分数与总体均值之间的距离.当原始分数低于平均值时, ...

  3. 【Python】圆周率的计算

    1.公式法  代码: #CalPiV1.py pi=0 N=100 for k in range(N): pi+=1/pow(16,k)*(\ 4/(8*k+1)-2/(8*k+4)-\ 1/(8*k ...

  4. Nginx防止DDOS流量攻击

    DDOS流量攻击:频繁的发送请求,造成宽带占用,其他客户端无法访问                Nginx解决DDOS流量攻击,利用limit_req_zone限制请求次数    limit_con ...

  5. C语言合并两个有序链表

    将两个有序链表合并为一个新的有序链表并返回.新链表是通过拼接给定的两个链表的所有节点组成的. 示例: 输入:1->2->4, 1->3->4输出:1->1->2-& ...

  6. leetcode top-100-liked-questions刷题总结

    一.起因 宅在家中,不知该做点什么.没有很好的想法,自己一直想提升技能,语言基础自不必言,数据结构还算熟悉,算法能力一般.于是乎,就去刷一通题. 刷题平台有很多,我选择了在leetcode进行刷题.回 ...

  7. JavaScript DOM–节点操作

    节点 节点至少拥有nodeType(节点类型).nodeName(节点名称)和nodeValue(节点值)这三个基本属性. 元素节点 nodeType  为1 属性节点 nodeType  为2 文本 ...

  8. 移动端安卓IOS对接H5项目遇到的坑

    最近做一个H5项目 退出的时候需要调用原生的弹框退出, 之前以为很简单,直接ajax调用后端退出接口即可.后来发现数据拿不到,因为状态用的是cookie存储, 后来想到ajax 传参数请求,后来还是拿 ...

  9. [SDOI2014] 重建 - 矩阵树定理,概率期望

    #include <bits/stdc++.h> #define eps 1e-6 using namespace std; const int N = 55; namespace mat ...

  10. Java EE开发课外事务管理平台

    Java EE开发课外事务管理平台 演示地址:https://ganquanzhong.top/edu 说明文档 一.系统需求 目前课外兴趣培训学校众多,完善,但是针对课外兴趣培训学校教务和人事管理信 ...