Codeforces 1335E1 - Three Blocks Palindrome (easy version)



題面

1335E1




題意

給定一個長度為 n 的數列

定義要求的回文子數列滿足下圖條件

PIC1

其中 x 與 y 可以為 0

即這個回文子數列可以是數字完全相同的一個子數列

也可以是只包含兩種數字,且其中一種平均分布在另一種數字的兩側

求出最長的回文子數列長度




解題思路

在輸入時往vector里記錄下每個數字出現的位置

然后開始枚舉位於兩側的數字的種類 i( i = 1 ~ 26 )

首先考慮這個回文子數列只包含一種數字,剛好根據枚舉

直接將答案與枚舉的字符數量取大(與枚舉到的vector[i].size取大)

然后如果出現的次數少於2次,說明不能放在兩側,直接跳過這次枚舉

否則,再對每一側擁有多少個數字 i 進行枚舉( j = 1 ~ size/2 )

然后根據貪心可以得知,假設枚舉出數字 i 在每一側出現 j 次的話

那就可以取原數列中最左邊 j 個 i 和最右邊 j 個 i 作為答案的原位置,再從左數第 j 個 i 的位置 +1 開始,以右數第 j 個 i 的位置 -1 結束,在這段區間內尋找出現最多次的數字 x 作為答案的中間部分,則這種情況的答案就是 j*2+x出現的次數


優化:

(如果用的是RMQ問題的 “靜態查找區間內出現最多次數字的次數” 的話可以不需要看這里的優化)

可以發現,在我們枚舉 j 的時候,可行的區間永遠是連續的

以【 1 1 1 2 1 2 1 1 】,枚舉數字 i = 1 時為例

PIC2

圖中綠色區域就是每一次我們需要找出現最多次數字時的區間


由圖可以得知

如果 j 從小到大枚舉,則我們查找的區間會在原來的基礎上縮短,即上圖從上往下

如果 j 從大到小枚舉,則我們查找的區間會在原來的基礎上延長,即上圖從下往上

所以我們可以在每次枚舉 j 時,記錄下查找的區間內每種數字出現的個數

在 j 改變時,把兩個狀態中出現改變的兩端區間處理下就可以直接轉移

那么這里的時間復雜度就從 O( size/2 * n ) 降為了 O(n)

詳見代碼




主要部分代碼實現

對於枚舉 j 時的轉移部分

因為狀態轉移時左右兩端都會加上或者減少一段區間

以 j 從大到小枚舉為例(代碼也是從大到小)

需要先處理中間部分,然后再每次增加兩段子區間

所以特殊處理下中間部分

假設當前枚舉的數字 i 出現的次數為 cnt

則兩端每端最多有 cnt/2 個數字 i ,以 cntt=cnt/2 記錄


因為vector中下標從 0 開始

所以實際范圍為 0 ~ cnt-1

如果cnt是偶數,則存在兩個中位數,分別是 cntt-1 和 cntt ,最開始以這兩個位置開始處理即可

而如果cnt是奇數,則中位數只存在一個,因為我們要保證兩端的數字個數相同,所以最中間這個數字不能取,所以最開始處理的是 cntt-1 和 cntt+1

發現實際上按照兩端數字相同的性質,可以直接采用 cntt-1cnt-cntt 即可,不需要特殊判斷奇偶


然后定義數組,尋找第 cntt-1 個數的位置 +1 到第 cnt-cntt 個數的位置 -1 這段范圍內的各種數字出現的次數

因為兩端個數為 cntt,則此時答案就是 最多次數mx+cntt*2

int num[30]={0},mx=0;
for(int j=v[i][cntt-1]+1;j<v[i][cnt-cntt];j++)
    num[ar[j]]++; //記錄
for(int j=1;j<=26;j++)
    if(num[j]>mx)
        mx=num[j]; //尋找出現次數最大的
ans=max(ans,mx+cntt*2);

然后就可以通過狀態轉移了

j 從 cntt-1 開始向下枚舉到 1

因為 j 代表的就是個數

所以每次增加的區間分別為 第 j-1 個數 到 第 j 個數第 cnt-j-1 個數 到 第 cnt-j 個數

直接加入上面的num數組即可,不需要清零

最后的答案為 mx+j*2

for(int j=cntt-1;j>0;j--)
{
    for(int k=v[i][j-1]+1;k<v[i][j];k++)
        num[ar[k]]++;
    for(int k=v[i][cnt-j-1]+1;k<v[i][cnt-j];k++)
        num[ar[k]]++;
    for(int k=1;k<=26;k++)
        if(num[k]>mx)
            mx=num[k];
    ans=max(ans,mx+j*2);
}



完整程序

感覺是純暴力加上個優化,結果跑得飛快

范圍修改下可以直接過hard

(31ms/3000ms)

#include<bits/stdc++.h>
using namespace std;
int ar[2050];
vector<int> v[30];

void solve()
{
    int n,ans=0;
    cin>>n;
    for(int i=1;i<=26;i++)
        v[i].clear(); //多組數據注意清空
    for(int i=1;i<=n;i++)
    {
        cin>>ar[i];
        v[ar[i]].push_back(i); //記錄每個數字出現的位置
    }
    for(int i=1;i<=26;i++) //枚舉位於兩側的數字
    {
        int cnt=v[i].size(),cntt;
        ans=max(ans,cnt); //單種數字作為答案的情況

        if(cnt<=1)
            continue; //如果只出現了一次或沒出現過,直接continue即可

        cntt=cnt/2; //位於某一側的數字的最大數量

        int num[30]={0},mx=0;

        for(int j=v[i][cntt-1]+1;j<v[i][cnt-cntt];j++) //第一次每一側的數量均為cntt,找的是第cntt-1個到第cnt-cntt個之間的位置
            num[ar[j]]++;
        for(int j=1;j<=26;j++)
            if(num[j]>mx)
                mx=num[j]; //尋找出現次數最多的次數
        ans=max(ans,mx+cntt*2); //記錄答案

        for(int j=cntt-1;j>0;j--) //然后枚舉每一側的數量
        {
            for(int k=v[i][j-1]+1;k<v[i][j];k++) //左邊的區間是從第j-1個到第j個
                num[ar[k]]++;
            for(int k=v[i][cnt-j-1]+1;k<v[i][cnt-j];k++) //右邊的區間是從第cnt-j-1個到第cnt-j個
                num[ar[k]]++;
            for(int k=1;k<=26;k++)
                if(num[k]>mx)
                    mx=num[k]; //尋找出現次數最多的次數
            ans=max(ans,mx+j*2); //記錄答案
        }
    }
    cout<<ans<<'\n';
}
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    int T;cin>>T;
    for(int t=1;t<=T;t++)
        solve();
    return 0;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM