タイトル
タイトル
長さnのシーケンスが与えられた
定義された回文サブシーケンスは次の条件を満たす
ここで、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の数を列挙します
図の緑色の領域は、最も頻繁な数を見つける必要がある間隔です
写真から見ることができます
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-1とcnt-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;
}