1. 程式人生 > 其它 >【簡】題解 吉首大學第十一屆“新星杯”大學生程式設計大賽

【簡】題解 吉首大學第十一屆“新星杯”大學生程式設計大賽

傳送門:OJ

OJ

前言

A K題的大模擬實在寫不動了摸掉了(其實是其他作業疊太多了)等有空了再說吧

寫得太爛 大佬們請不要介意

問題 A 咱倆下象棋去:

給你一個象棋盤 棋子的個數可能跟普通的不同問能否在2步內將對方的軍

QWQ:

A掉的大佬tql

考場一看是到大模擬果斷跳

問題 B 抽積木遊戲

給你豎著的n個積木當積木的長度能被高度加1整除可抽出

給定積木堆問是否能被抽完

QWQ:

顯然上面的積木不會對下面積木造成影響

對於某個高度的積木

在最優抽出的過程中 一定能保證對於其高度及其以下的位置 在某個時刻能夠達到

那麼對於該積木如果能被其高度及其以下的任意一高度整除那麼該積木就可抽出

考慮從底層往上列舉 用cup記錄該數能否被抽

設高度為x

如果x為質數那麼將x*Z都可以取到

如果非質數那麼一定被之前某個質數的更新所覆蓋

最終的流程就是

線性篩預處理質數

從底層往上列舉

判斷該高度積木能否抽出

更新cup標記能取到的值

輸出結果

#include<bits/stdc++.h> 
using namespace std;
#define ll long long
#define C getchar()-48
inline ll read()
{
    ll s=0,r=1;
    char c=C;
    for(;c<0||c>9;c=C) if(c==-3) r=-1;
    for
(;c>=0&&c<=9;c=C) s=(s<<3)+(s<<1)+c; return s*r; } const int N=2e6+10; int n,mx; int a[N],pd[N]; int prime[N],top,vis[N]; void xxs(int mx)//線性篩 { vis[0]=vis[1]=1; //1為不是0為是 for(int i=2;i<=mx;i++) { if(!vis[i]) prime[++top]=i; for(int j=1;j<=top&&prime[j]*i<=mx;j++) { vis[prime[j]
*i]=1; if(!(i%prime[j])) break; } } } int main() { n=read();xxs(N-10); for(int i=1;i<=n;i++) a[i]=read(),mx=max(mx,a[i]); for(int i=1;i<=n;i++) { int del=i+1; if(!vis[del]) for(int j=del;j<=N-10;j+=del) pd[j]=1; if(!pd[a[i]]){printf("NO");return 0;} } printf("YES"); return 0; }

問題 C 梅賈的竊魂卷

給個層數最高0層最高25次

k +5次 d -10次 a -2次

給定kda個數

問有多少種排列方法使得最後層數為25次

QWQ:

剛開始被原題意誤導說大於等於25次 以為層數能大於25錯了好幾次 浪費了一段時間

直接考慮dp emmm不知道為什麼我開始還懷疑了正確性考慮正做還是反做還是用廣搜做

資料範圍有限可直接設dp[i][j][k][v]前三位表示各用了多少次一維表示當前的值

記得用maxmin限制更新時v更新時的上下界

#include<bits/stdc++.h> 
using namespace std;
#define ll long long
#define C getchar()-48
inline ll read()
{
    ll s=0,r=1;
    char c=C;
    for(;c<0||c>9;c=C) if(c==-3) r=-1;
    for(;c>=0&&c<=9;c=C) s=(s<<3)+(s<<1)+c;
    return s*r;
} 
const ll N=100;
ll k,d,a,ans;
ll dp[20][10][10][70];
int main()
{
    k=read(),d=read(),a=read();
    dp[0][0][0][0]=1;
    for(ll i=0;i<=k;i++)
    for(ll j=0;j<=d;j++)
    for(ll k=0;k<=a;k++) 
    for(ll v=0;v<=25;v++) 
    {
        dp[i+1][j][k][min(25*1ll,v+5*1ll)]+=dp[i][j][k][v];
        dp[i][j+1][k][max(0*1ll,v-10*1ll)]+=dp[i][j][k][v];
        dp[i][j][k+1][min(25*1ll,v+2*1ll)]+=dp[i][j][k][v];
    }
    for(ll i=25;i<=25;i++) ans+=dp[k][d][a][i];
    cout<<ans;
    return 0;
}

問題 D 強行數學題

給2個式子求另個式子

QWQ:

沒什麼好說的真的強行數學題

(2)*2-(1)=(3)

#include<bits/stdc++.h> 
using namespace std;
#define ll long long
#define C getchar()-48
inline ll read()
{
    ll s=0,r=1;
    char c=C;
    for(;c<0||c>9;c=C) if(c==-3) r=-1;
    for(;c>=0&&c<=9;c=C) s=(s<<3)+(s<<1)+c;
    return s*r;
} 
#define db double
const db pi=3.1415926;
int main()
{
    printf("%.3lf",pi*pi/12.0);
    return 0;
}

問題 E 羊村保衛站

給你個方陣的左上角位置和邊長 給你個點

問以改點為圓心最小半徑多少能覆蓋矩陣

QWQ:

(毒)瘤)

不得不提該題的座標軸x向右y軸向上不是預設的座標軸

考場考場反反覆覆看了題目好幾遍 最開始還以為是貪心錯了看時間範圍夠把邊長也都算了進去

時間消失法術 心態差點爆炸 我記住你了

顯然只要保證方陣四個頂點能被包括就可以了

不夠就算邊長也算也不會超時

#include<bits/stdc++.h> 
using namespace std;
#define ll long long
#define C getchar()-48
inline ll read()
{
    ll s=0,r=1;
    char c=C;
    for(;c<0||c>9;c=C) if(c==-3) r=-1;
    for(;c>=0&&c<=9;c=C) s=(s<<3)+(s<<1)+c;
    return s*r;
}
#define db double
ll xx,yy,d;
ll x,y;
db ans;
inline db js(ll xx,ll yy,ll x,ll y)
{
    return sqrt(1.0*abs(xx-x)*abs(xx-x)+1.0*abs(yy-y)*abs(yy-y));
}
int main()
{
    xx=read(),yy=read(),d=read();
    x=read(),y=read();
////    cout<<js(xx,yy,x,y)<<endl;
//    for(ll i=0;i<=d;i++) ans=max(ans,js(xx+i,yy,x,y));
//    for(ll i=0;i<=d;i++) ans=max(ans,js(xx,yy+i,x,y));
//    for(ll i=0;i<=d;i++) ans=max(ans,js(xx+d,yy+i,x,y));
//    for(ll i=0;i<=d;i++) ans=max(ans,js(xx+i,yy+d,x,y));
    db tmp=
    max(
    max(js(xx+d,yy,x,y),js(xx,yy-d,x,y))
    ,
    max(js(xx,yy,x,y),js(xx+d,yy-d,x,y))
    );
    ans=max(ans,tmp);
    printf("%.3lf",ans);
    return 0;
}

問題 F 《特殊三角形》

給你幾個矩陣觀察規律

求第n個矩陣

QWQ:

考場看也是大模擬也跳了下

後來其他題目做差不多回來看只剩半小時結束差幾分鐘才調出來

最後這題發現交了總時間還是比前面的人總時間成排名沒變化

觀察得知圖形是三角形往左下和右下複製一遍重疊一部分

按意模擬冷靜

#include<bits/stdc++.h> 
using namespace std;
#define ll long long
#define C getchar()-48
inline ll read()
{
    ll s=0,r=1;
    char c=C;
    for(;c<0||c>9;c=C) if(c==-3) r=-1;
    for(;c>=0&&c<=9;c=C) s=(s<<3)+(s<<1)+c;
    return s*r;
} 
const int N=1300;
int n,w,md;
int stx,sty,len;
char a[N][N];
inline void cp(int x,int y,int len)
{
    for(int i=0;i<len;i++)
    for(int j=0;j<2*len;j++)
    if(a[x+i][y+j]==' ')
      a[x+i][y+j]=a[stx+i][sty+j];
}
int main()
{
    n=read();w=(1<<n)+1;
    for(int i=1;i<=w;i++)
    for(int j=0;j<=2*w-1;j++)
      if(a[i][j]!='*') a[i][j]=' ';
    a[1][w]='*';
    a[2][w-1];a[w][w-2]='*';
    stx=1,sty=w-1;len=(1<<0)+1;//*長 
    for(int cs=1;cs<=n;cs++)
    {
        int tmpx=stx+len-1,tmpy=sty-len+1;
        cp(tmpx,tmpy,len);
        tmpx=stx+len-1,tmpy=w;
        cp(tmpx,tmpy,len);
        stx=1;sty=sty-len+1;
        len=((1<<cs)+1); 
    }
    for(int i=1;i<=w;i++,puts(""))
    for(int j=0;j<=2*w-1;j++)
      cout<<a[i][j];
    return 0;
}

問題 G 套馬的漢子

求n矩陣對於其中某個位置在每個矩陣中都是橫縱的最小點就標記

問有幾個標記

QWQ:

看到第一反應二維線段樹?

看見字首和可以直接做

後來發現數據範圍太小 就算直接寫暴力列舉也能過

做的時候不知道為什麼題目又沒看清寫了最大改回最小又浪費了一段時間

寫的時候 因為還是字首和更好寫就寫了這個

上下前後左右都列舉下用遍字首和標記下是否為最小

統計答案

#include<bits/stdc++.h> 
using namespace std;
#define ll long long
#define C getchar()-48
inline ll read()
{
    ll s=0,r=1;
    char c=C;
    for(;c<0||c>9;c=C) if(c==-3) r=-1;
    for(;c>=0&&c<=9;c=C) s=(s<<3)+(s<<1)+c;
    return s*r;
} 
const int N=20,V=110;
int n,x,y,mn,ans;
int a[N][N][N],pd[N][N];
int main()
{
    n=read();x=read(),y=read();
    for(int i=1;i<=x;i++)
    for(int j=1;j<=y;j++)
      pd[i][j]=1;
    for(int k=1;k<=n;k++)
    for(int i=1;i<=x;i++)
    for(int j=1;j<=y;j++)
      a[k][i][j]=read();
    for(int k=1;k<=n;k++)
    {
        for(int i=1;i<=x;i++)
        {
            mn=1e9+7;
            for(int j=1;j<=y;j++)
            {
                mn=min(mn,a[k][i][j]);
                if(a[k][i][j]>mn) pd[i][j]=0;
            }
            mn=1e9+7;
            for(int j=y;j>=1;j--)
            {
                mn=min(mn,a[k][i][j]);
                if(a[k][i][j]>mn) pd[i][j]=0;
            }
        }
        for(int j=1;j<=y;j++)
        {
            mn=1e9+7;
            for(int i=1;i<=x;i++)
            {
                mn=min(mn,a[k][i][j]);
                if(a[k][i][j]>mn) pd[i][j]=0;
            }
            mn=1e9+7;
            for(int i=x;i>=1;i--)
            {
                mn=min(mn,a[k][i][j]);
                if(a[k][i][j]>mn) pd[i][j]=0;
            }
        }
    }
    
//    for(int i=1;i<=x;i++,puts(""))
//    for(int j=1;j<=y;j++) 
//      cout<<pd[i][j]<<" ";
//    
    for(int i=1;i<=x;i++)
    for(int j=1;j<=y;j++) if(pd[i][j])
      ans++;
    cout<<ans;
    return 0;
}

問題 H 繁忙的路口

一個紅綠燈入口

給定一個狀態問是否矛盾

QWQ:

顯然對於每個入口來說是等價的

強制對其中單一入口考慮

分別考慮當前路口直行有哪些矛盾左行有哪些矛盾

左行有5種矛盾 直行有4種矛盾(考場 剛開始糾結左行和右口的左行是否有矛盾後來交了下確實是有矛盾的)

再對於每個入口都這樣做一遍判斷有沒有矛盾

這裡對於每個入口做一遍的時候推薦用迴圈陣列列舉每個入口

具體實現請參考程式碼 0表示左行 1表示直行

#include<bits/stdc++.h> 
using namespace std;
#define ll long long
#define C getchar()-48
inline ll read()
{
    ll s=0,r=1;
    char c=C;
    for(;c<0||c>9;c=C) if(c==-3) r=-1;
    for(;c>=0&&c<=9;c=C) s=(s<<3)+(s<<1)+c;
    return s*r;
} 
char s[20];
inline int del(int x)
{
    x=(x+24)%4;
    if(x==0) x+=4;
    return x;
}
inline int dw(int x,int y)
{
    return (x-1)*2+y+1;
}
int main()
{
    scanf("%s",s+1);
    for(int x=1;x<=4;x++)
    {
        if(s[dw(x,0)]=='G')
        {
            if(s[dw(del(x+1),0)]=='G'){cout<<"terrible";return 0;}
            if(s[dw(del(x+3),1)]=='G'){cout<<"terrible";return 0;}
            if(s[dw(del(x+3),0)]=='G'){cout<<"terrible";return 0;}
            if(s[dw(del(x+2),1)]=='G'){cout<<"terrible";return 0;}
            if(s[dw(del(x+2),0)]=='G'){cout<<"terrible";return 0;}
        }
        if(s[dw(x,1)]=='G')
        {
            if(s[dw(del(x+1),0)]=='G'){cout<<"terrible";return 0;}
            if(s[dw(del(x+1),0)]=='G'){cout<<"terrible";return 0;}
            if(s[dw(del(x+2),0)]=='G'){cout<<"terrible";return 0;}
            if(s[dw(del(x+3),1)]=='G'){cout<<"terrible";return 0;}
        }
    }
    cout<<"perfect";
    return 0;
}

問題 I roll up

給你120個菜 6個人

拿最多的人和最小的人都被淘汰

第一個人拿的量已給出

後每個人先保證不被淘汰再保證淘汰的人最多再保證自己拿的菜儘可能多

菜不必取完

QWQ:

因為第一個人的量a給出 第二個人就比較特殊知道前面的人的值

考慮對a分類討論

1 a>58 保證a肯定最大得出a>58 第二個人儘量取 其他人全1

2 a==58 結果a-1 2 1 1

3(20<a&&a<58) 因為平分每個人20 該範圍此時保證 a非最小 後面人儘量取a-1為非最大 保證最後個人取最小

4 a<=20

考場的時候這邊不確定考慮到答案可能有2種情況 因為菜不必取完第二個人無論取多少都不能保證不被淘汰

1 因為第二人不能保證不被淘汰 所以保證可能性儘可能大淘汰儘可能多 取a 後幾個人也這麼做最後所有人都取a所有人一起淘汰

按這樣的推理到最後個人最後的人發現前面人取總肯定不會 和 前面三種的任意一種相同

那麼得知a取值情況必為第四種 那麼按推理前面人都是a 最後個人按取的優先順序必定把剩下的取完淘汰的還是6個人

而第二的人想到前面的情況也沒法改變取值保證淘汰人數6不變取值變多

交上去發現這種方法錯了

2 因為第二人不能保證不被淘汰 在第二人取儘可能多時保證淘汰人數5人並且取值最大

交上去發現從從50到71分了 發現std是這種情況

考場上不知道為什麼情況3的58打成28又搞來搞查了半天以為其他幾個直接輸出的結果有問題

最後每種情況都單獨交了下才查出來

#include<bits/stdc++.h> 
using namespace std;
#define ll long long
#define C getchar()-48
inline ll read()
{
    ll s=0,r=1;
    char c=C;
    for(;c<0||c>9;c=C) if(c==-3) r=-1;
    for(;c>=0&&c<=9;c=C) s=(s<<3)+(s<<1)+c;
    return s*r;
} 
const int N=120;
int a;
int b[N];
int main()
{
    a=read();
    if(a>58) cout<<N-a-4<<" "<<1<<" "<<1<<" "<<1<<" "<<1; //33
    if(a==58) cout<<a-1<<" "<<2<<" "<<1<<" "<<1<<" "<<1; 
    if(20<a&&a<58)//20-28 17 38
    {
        int sum=a;
        for(int i=1;i<=5;i++)
        {
            b[i]=min(N-sum-(5-i),a-1);
            sum+=b[i];
        }
        cout<<b[1];
        for(int i=2;i<=5;i++) cout<<" "<<b[i];
    } 
//    if(a==20) cout<<20<<" "<<20<<" "<<20<<" "<<20<<" "<<20; 
//    if(a<20) cout<<a<<" "<<a<<" "<<a<<" "<<a<<" "<<a; 
    if(a<=20) cout<<N-a-4<<" "<<1<<" "<<1<<" "<<1<<" "<<1; //21
    return 0;
}

問題 J 羊村快遞站

給個數列 每次按給定的位置取最後輸出取的次序沒取為0被刪掉為-1

如果對與一個還未取的位置如 果右邊先被取了左邊再被取了那麼該位置被刪掉

QWQ:

考慮按取的次序一個個處理

用個指標r 標記到當前情況最有邊已經被取的位置

用樹狀陣列處理被刪的情況

對於當前取的位置w

1 r<=w 更新指標更新答案

2 w<r

如果沒被刪 更新答案用區間-1 更新w+1到r-1值

被刪了跳過或者更新答案

最後判斷哪些位置沒更新答案並且值小於0表示被刪掉了更新答案

考場 沒找到自己樹狀陣列模板太久沒寫了以防萬一

隨便找了板子 碼風有點奇怪(考後改掉了)

#include<bits/stdc++.h> 
using namespace std;
#define ll long long
#define C getchar()-48
inline ll read()
{
    ll s=0,r=1;
    char c=C;
    for(;c<0||c>9;c=C) if(c==-3) r=-1;
    for(;c>=0&&c<=9;c=C) s=(s<<3)+(s<<1)+c;
    return s*r;
} 
const int N=1e4+10;
int n,m,l,r,topa;
int a[N],b[N],mp[N],tr[N],ans[N];
inline void add(int x,int v)
{
    for(;x<=n;x+=x&(-x)) tr[x]+=v;
}
inline int ask(int x)
{
    int ans=0;
    for(;x;x-=x&(-x)) ans+=tr[x];
    return ans; 
}
int main()
{
    n=read();r=0;l=n+1;
    for(int i=1;i<=n;i++) a[i]=read(),mp[a[i]]=i;
    m=read();
    for(int i=1;i<=m;i++) b[i]=read();
    for(int i=1;i<=m;i++)
    {
        int w=mp[b[i]];
        if(r<=w){r=w,ans[w]=++topa;continue;}//l=w-1;
        if(w<r)
        {
            if(ask(w)>=0)
            {
                ans[w]=++topa;
                add(w+1,-1);add(r,1);
                continue;
            }
        }
    }
    for(int i=1;i<=n;i++) if(!ans[i]) if(ask(i)<0) ans[i]=-1;
    for(int i=1;i<=n;i++) printf("%d ",ans[i]);
    return 0;
}

問題 K 推箱子

給你個推箱子的地圖 問最小步數最小方案數最小步數的最小字典序的方案

QWQ:

A掉的大佬tql

考場一看是到大模擬果斷跳

問題 L Alice and Bob

給你n個有編號的石子每次只能取與編號與當前最大編號互質的石子每次最多取n個

兩個人取問誰贏

QWQ:

emmmm怎麼說呢原題的 只有唯一公約數怎麼看怎麼奇怪

考場竟然一直沒意識到就是互質

知道意思後發現最大的石子除了n==1其他情況是永遠取不到的

那麼能取的石子是固定

原題就轉化為

1 給你一個數n求小於等於n的與n互質的數x

2給你個x個數的石子每次限取m個問誰贏

說來慚愧 考後寫的時候 已經是完全忘記了尤拉函式的定義就是 問1了 數論學得太不紮實了

線性篩oula函式就解決了問1

問2就是經典的取石子問題

1 x%(m+1)==0 那麼無論先手取什麼後手總能取到一個數 使此輪兩人加起來取到m+1最後 後手勝

2 x%(m+1)!=0 那麼先手可以肯定可以一定數 使得剩下來的數被(m+1)整除轉化為情況1 先手勝

#include<bits/stdc++.h> 
using namespace std;
#define ll long long
#define C getchar()-48
inline ll read()
{
    ll s=0,r=1;
    char c=C;
    for(;c<0||c>9;c=C) if(c==-3) r=-1;
    for(;c>=0&&c<=9;c=C) s=(s<<3)+(s<<1)+c;
    return s*r;
} 
ll n,m; 
const ll N=1e7+10;
int vis[N],prime[N],top,oula[N];
void xxoula(ll mx)
{
    vis[0]=vis[1]=1;//1為不是0為是 
    oula[0]=0;oula[1]=1;
    for(ll i=2;i<=mx;i++)
    {
        if(!vis[i]){prime[++top]=i;oula[i]=i-1;}
        for(ll j=1;j<=top&&prime[j]*i<=mx;j++)
        {
            vis[prime[j]*i]=1;
            if(!(i%prime[j])){oula[prime[j]*i]=oula[i]*prime[j];break;}
            oula[prime[j]*i]=oula[i]*(prime[j]-1);
        }
    }
} 
int main()
{
    xxoula(1e7);
    ll t=read();
    while(t--)
    {
        n=read(),m=read();
        if((oula[n])%(m+1)) printf("Alice\n");
        else printf("Bob\n");
    }
    return 0;
}

問題 M 簡單字串匹配

給你兩個串a,b 問b移位後和對a的最大貢獻

ABCD組成一個環的順序後項對前一項 算1的貢獻

QWQ:

感覺這場考試收穫最大的就是這題了重新理解了fft的優化

考慮對a預處理直接把每個位置的值按ABCD迴圈加1變換

題目就轉換成 b移位後和對a的最大貢獻位置上值相等算1貢獻

接著就感覺特別熟悉

看到題目反應套用kmp的思想處理資訊

然後就發現這樣的話資訊極難維護

然後就想到了bitset

對ABCD各單獨考慮貢獻

該位置有 bitset中該值就為1

統計貢獻就是a b的bitset取&

太久沒用完全忘記了一次複雜度是n/32級別的都是拿來卡常用的

考場以為複雜度是lgn級別寫完後喜獲暴力的分數33分複雜度 O(nn/32)

考場後看到了某個大佬發的連結說用fft優化

大佬的程式碼太長了懶得仔細看了就自己寫了

考慮原來複雜度的瓶頸是統計貢獻n*n/32

對於 單一的一個值 假設m=6

統計貢獻的情況是

相當上下兩數列各位相乘

而對與多項式卷積來說

x+6+x+1維值的計算相當於下圖

那麼將b陣列前後翻轉下就通過卷積就可以得到 該情況的貢獻了

移動各個長度答案也就是卷積後的各個維度

用fft優化卷積 對於 單一的一個值移動各個長度的的貢獻就能求出來複雜度nlgn

4個值總複雜度 O(4*nlgn)

注意每次fft的時候初始化卷積的ab陣列到記得初始到最小的2^x>n+m

另外題目中 m+移動的長度 是允許大於n的(允許兩邊都能空的賽馬真的太奇怪了)

關於之前的暴力用的stl如果stl用int實現的話手寫個ll的除/64的常數

因為答案只要最優的一個移動位置就能計算了縮小下移動列舉的量

用+rand()列舉移動位置 資料水點機器快點多交幾遍運氣好點說不定能過

bitset程式碼

#include<bits/stdc++.h> 
using namespace std;
#define ll long long
#define C getchar()-48
inline ll read()
{
    ll s=0,r=1;
    char c=C;
    for(;c<0||c>9;c=C) if(c==-3) r=-1;
    for(;c>=0&&c<=9;c=C) s=(s<<3)+(s<<1)+c;
    return s*r;
} 
const int N=1e5+10;
int n,m,ans;
char a[N],b[N];
bitset<N> bt1[6],bt2[6],bt3[6];
inline int del(int x)
{
    x=x%4;
    if(x==0) x=4; 
    return x;
}
int main()
{
    n=read(),m=read();
    scanf("%s",a+1);scanf("%s",b+1);
    for(int i=1;i<=n;i++) bt1[del(a[i]-'A'+2)][i]=1;
    for(int i=1;i<=m;i++) bt2[del(b[i]-'A'+1)][i]=1;
    for(int i=1;i<=n;i++)
    {
        int tmp=0;
        for(int k=1;k<=4;k++)
        {
            bt3[k]=bt1[k]&(bt2[k]<<i);
            tmp+=bt3[k].count();
        }
        ans=max(ans,tmp);
    }
    cout<<ans;
}

fft程式碼

#include<bits/stdc++.h> 
using namespace std;
#define ll long long
#define C getchar()-48
inline ll read()
{
    ll s=0,r=1;
    char c=C;
    for(;c<0||c>9;c=C) if(c==-3) r=-1;
    for(;c>=0&&c<=9;c=C) s=(s<<3)+(s<<1)+c;
    return s*r;
} 
const int N=3e5+10;
int nn,mm,ans;
char aa[N],bb[N];
int bt1[6][N],bt2[6][N],bt3[6][N];
inline int del(int x)
{
    x=x%4;
    if(x==0) x=4; 
    return x;
}
#define E complex<double> 
//N記得至少開兩倍 
const double pi=acos(-1);
int n,m,l,r[N];
E a[N],b[N];
void fft(E *a,int f){
    for(int i=0;i<n;i++)if(i<r[i])swap(a[i],a[r[i]]);//交換位置  if為了避免重複交換變回原來的 
    for(int i=1;i<n;i<<=1){//當前合併兩個長度為i的值的集合 
        E wn(cos(pi/i),f*sin(pi/i));//單位復根 將一個圓分成i部分 因為每次要合併i對下標為奇數和歐素的 
        for(int p=i<<1,j=0;j<n;j+=p){//當前要合併區間的第一個位置p   
            E w(1,0);
            for(int k=0;k<i;k++,w*=wn){//要合併這個區間的第幾個數 
                E x=a[j+k],y=w*a[j+k+i];
                a[j+k]=x+y;a[j+k+i]=x-y;  //算出帶進去的兩個值的結果 
            }
        }
    }
}
inline void cl()
{
    n=nn,m=mm;l=0;
    for(int i=0;i<=N-10;i++) r[i]=0;
    for(int i=0;i<=N-10;i++) a[i]=b[i]=0;
}
int main()
{
    nn=read(),mm=read();
    scanf("%s",aa+1);scanf("%s",bb+1);
    for(int i=1;i<=nn;i++) bt1[del(aa[i]-'A'+2)][i]=1;
    for(int i=1;i<=mm;i++) bt2[del(bb[i]-'A'+1)][i]=1;
    for(int k=1;k<=4;k++)//
    {
           cl(); 
        m+=n;for(n=1;n<=m;n<<=1)l++;//乘運算後的長度至少為n+m  運算要求為2的整次冪
        for(int i=0;i<n;i++)r[i]=(r[i>>1]>>1)|((i&1)<<(l-1));//蝴蝶操作 
        for(int i=1;i<=nn;i++) a[i]=bt1[k][i];
        for(int i=1;i<=mm;i++) b[i]=bt2[k][mm-i+1];;
        fft(a,1);fft(b,1);//轉化為點值表達 
        for(int i=0;i<=n;i++)a[i]=a[i]*b[i];//O(n)運算 
        fft(a,-1);//轉化為係數表達 
        for(int i=0;i<=m;i++) bt3[k][i]=(int)(a[i].real()/n+0.5);;
    }    
    for(int i=0;i<=nn;i++)
    {
        int tmp=0;
        for(int k=1;k<=4;k++) tmp+=bt3[k][1+mm+i];
        ans=max(ans,tmp);
    }
    cout<<ans;
}