Codeforces 1335E1-Three Blocks Palindrome(easy version)


タイトル

1335E1




タイトル

長さnのシーケンスが与えられた

定義された回文サブシーケンスは次の条件を満たす

PIC1

ここで、xとyは0にすることができます

つまり、この回文のサブシーケンスは、まったく同じ番号のサブシーケンスにすることができます。

2つの数値のみを含めることもでき、そのうちの1つは他の数値の両側に均等に分散されます

最長の回文サブシーケンスの長さを見つける




問題解決のアイデア

入力時にベクター内の各番号が表示される位置を記録します

次に、両側の数値の型 i の列挙を開始します(i = 1〜26)

最初に、このパリンドロームのサブシーケンスには、列挙に基づいて、1種類の数しか含まれていないことを考慮してください

回答と列挙型の文字数を直接増やします(列挙型のベクトル[i] .sizeよりも大きい)。

次に、出現回数が2回未満の場合、それを両側に配置できないことを意味し、この列挙を直接スキップします

それ以外の場合は、両側にある桁数を列挙します(j = 1〜サイズ/ 2)

次に、列挙番号iが両側にj回出現する場合、greedから知ることができます

次に元のシーケンスの左端のj iと右端のj i回答の元の位置として、左からj番目のi位置から始めて+1、右から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が変化すると、変化した2つの状態の両端を直接転送できます。

次に、ここでの時間の複雑さはO(サイズ/ 2 * n)からO(n)に削減されます。

詳細についてはコードを参照してください




コードの主要部分

j列挙時の遷移部分

状態が遷移すると、左端と右端がセクションを追加または削減するため

例として、jの列挙を大から小にしてみましょう(コードも大から小に)

最初に中間部分を処理してから、一度に2つのサブインターバルを追加する必要があります

だから特別な扱いの下の中央部分

現在の列挙された数iの出現数がcntであると仮定します

次に、両端に最大cnt / 2桁のiがあり、cntt = cnt / 2として記録さます。


ベクトルの添え字が0から始まるため

したがって、実際の範囲は0〜cnt-1です

cntが偶数の場合、中央値はcntt-1とcnttの2つで、最初に処理できます。

cntが奇数の場合、両端の数が同じになるようにしたいので中央値は1つだけなので、中央の数を取得できないため、最初の処理は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-番号に最初の番号j-1およびCNT-jの数に第2の数CNT-J-1

上記の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);
}



完全なプログラム

感情は純粋な暴力と最適化であり、結果は速く実行されています

範囲の変更の下では、ハードに直接渡すことができます

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

おすすめ

転載: www.cnblogs.com/stelayuri/p/12695384.html