CodeForces Gym 100213F Counterfeit Money

来源:https://www.cnblogs.com/ycx-akioi/archive/2020/02/13/CodeForces-Gym-100213F.html
-Advertisement-
Play Games

"CodeForces Gym題目頁面傳送門" 有$1$個$n1\times m1$的字元矩陣$a$和$1$個$n2\times m2$的字元矩陣$b$,求$a,b$的最大公共子矩陣。輸出這個最大公共子矩陣的行數、列數和左上角分別在$a,b$中的坐標。若無解,輸出$``\text{0 0''}$。若 ...


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;
}

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1.前提條件 1). 確保已經安裝需要的Python版本 2). 確保已經將Python的目錄加入到環境變數中 2. Python安裝包的幾種常用方式 1). pip安裝方式(正常線上安裝) 2). whl安裝方式(離線安裝),一般是.whl格式的包 3). 源碼安裝方式(離線安裝),tar.gz/ ...
  • 概念: 什麼是REST? REST是Representational State Transfer的縮寫。翻譯為"表現層狀態轉化",restful是一種介面設計風格,它不是一個協議,通常是基於HTTP協議的; 為什麼需要這麼一個風格呢? RESTful的重點之一就是統一的介面命名規則; 每個開發者可 ...
  • 請求限制 一些情況下我們可能需要對請求進行限制,比如僅允許POST,GET等... RequestMapping註解中提供了多個參數用於添加請求的限制條件 value 請求地址 path 請求地址 method 請求方法 headers 請求頭中必須包含指定欄位 params 必須包含某個請求參數 ...
  • 一.用字典映射代替switch case語句 if/else可以代替switch但是非常不合適。 用字典代替switch: day = 5 switcher = { 0:'Sunday', 1:'Monday', 2:'Tuesday' } day_name = switcher.get(day,' ...
  • 我一直想用 Python and Selenium 創建一個網頁爬蟲,但從來沒有實現它。 幾天前, 我決定嘗試一下,這聽起來可能是挺複雜的, 然而編寫代碼從 Unsplash 抓取一些美麗的圖片還是挺容易的。 PS:很多人在學習Python的過程中,往往因為遇問題解決不了或者沒好的教程從而導致自己放 ...
  • 作為非專業的python選手,或者非專業的爬蟲選手,即使我們有一些編程基礎,有時想通過代碼從網上獲取一些信息,也不能徒手就能做,需要借鑒一些成熟的方案、代碼。 ...
  • 前言 亂碼是我們在程式開發中經常碰到且讓人頭疼的一件事,尤其是我們在做javaweb開發,如果我們沒有清楚亂碼產生的原理,碰到亂碼問題了就容易摸不著頭腦,無從下手。 亂碼主要出現在兩部分,如下: 第一,瀏覽器通過表單提交到後臺,如果表單內容有中文,那麼後臺收到的數據可能會出現亂碼。 第二,後端伺服器 ...
  • 資源訪問介面 由於JDK提供的資源訪問類並不能很好的滿足底層資源的訪問需求,所以Spring設計了一個Resource介面。Spring框架使用Resource裝載各種資源,包括配置文件資源、國際化屬性文件資源等 Resource具體的實現類圖 Resource介面的主要方法 1. boolean ...
一周排行
    -Advertisement-
    Play Games
  • Dapr Outbox 是1.12中的功能。 本文只介紹Dapr Outbox 執行流程,Dapr Outbox基本用法請閱讀官方文檔 。本文中appID=order-processor,topic=orders 本文前提知識:熟悉Dapr狀態管理、Dapr發佈訂閱和Outbox 模式。 Outbo ...
  • 引言 在前幾章我們深度講解了單元測試和集成測試的基礎知識,這一章我們來講解一下代碼覆蓋率,代碼覆蓋率是單元測試運行的度量值,覆蓋率通常以百分比表示,用於衡量代碼被測試覆蓋的程度,幫助開發人員評估測試用例的質量和代碼的健壯性。常見的覆蓋率包括語句覆蓋率(Line Coverage)、分支覆蓋率(Bra ...
  • 前言 本文介紹瞭如何使用S7.NET庫實現對西門子PLC DB塊數據的讀寫,記錄了使用電腦模擬,模擬PLC,自至完成測試的詳細流程,並重點介紹了在這個過程中的易錯點,供參考。 用到的軟體: 1.Windows環境下鏈路層網路訪問的行業標準工具(WinPcap_4_1_3.exe)下載鏈接:http ...
  • 從依賴倒置原則(Dependency Inversion Principle, DIP)到控制反轉(Inversion of Control, IoC)再到依賴註入(Dependency Injection, DI)的演進過程,我們可以理解為一種逐步抽象和解耦的設計思想。這種思想在C#等面向對象的編 ...
  • 關於Python中的私有屬性和私有方法 Python對於類的成員沒有嚴格的訪問控制限制,這與其他面相對對象語言有區別。關於私有屬性和私有方法,有如下要點: 1、通常我們約定,兩個下劃線開頭的屬性是私有的(private)。其他為公共的(public); 2、類內部可以訪問私有屬性(方法); 3、類外 ...
  • C++ 訪問說明符 訪問說明符是 C++ 中控制類成員(屬性和方法)可訪問性的關鍵字。它們用於封裝類數據並保護其免受意外修改或濫用。 三種訪問說明符: public:允許從類外部的任何地方訪問成員。 private:僅允許在類內部訪問成員。 protected:允許在類內部及其派生類中訪問成員。 示 ...
  • 寫這個隨筆說一下C++的static_cast和dynamic_cast用在子類與父類的指針轉換時的一些事宜。首先,【static_cast,dynamic_cast】【父類指針,子類指針】,兩兩一組,共有4種組合:用 static_cast 父類轉子類、用 static_cast 子類轉父類、使用 ...
  • /******************************************************************************************************** * * * 設計雙向鏈表的介面 * * * * Copyright (c) 2023-2 ...
  • 相信接觸過spring做開發的小伙伴們一定使用過@ComponentScan註解 @ComponentScan("com.wangm.lifecycle") public class AppConfig { } @ComponentScan指定basePackage,將包下的類按照一定規則註冊成Be ...
  • 操作系統 :CentOS 7.6_x64 opensips版本: 2.4.9 python版本:2.7.5 python作為腳本語言,使用起來很方便,查了下opensips的文檔,支持使用python腳本寫邏輯代碼。今天整理下CentOS7環境下opensips2.4.9的python模塊筆記及使用 ...