简要题意:
每次可以在数组中插入一个数(可以在两端)或删除一个数,改变一个数;求让数组成为回文数组的最小步数。
这题的蓝太假了,水 ( ext{dp}),想到算法就可以做出。
关键还是要想到用 动态规划 啊,要是想不到这题就做不出来了。
(考场上可以先用搜索,然后记忆化推导也行,但是这里就不推了)
显然考虑区间 ( ext{dp}).
用 (f_{i,j}) 表示将 (a_i) 到 (a_j) 变成回文的最小步数。
那么有:
[ f_{i,j} =
egin {cases}
f_{i+1,j-1} , s_i = s_j \
min(f_{i+1,j} , f_{i,j-1} , f_{i+1,j-1}) , s_i
ot = s_j \
end{cases}
]
下面一一解释。
如果 (s_i = s_j),则说明已经回文了一位,直接考虑砍掉它们的答案。
(因为回文串两边添上或去掉一个相同的字符,仍然具有回文性)
否则,(f_{i+1,j}) 是去掉第 (i) 位的方案,(f_{i,j-1}) 是去掉第 (j) 位的方案。
(f_{i+1,j-1}) 是改变 第 (i) 位或第 (j) 位使得 (s_i = s_j),然后砍掉它们的答案。
均需要一步,所以最后 (+1).
你会问:添加操作没有用?
你想,假设你在某个位置添加一个数,等价于在其对应的位置去掉一个数。
这仍然是因为上述的回文性质。在对应的位置删除或添加一个相同的数,不影响回文性质。
时间复杂度:(O(n^2)).
实际得分:(100pts).
#pragma GCC optimize(2)
#include<bits/stdc++.h>
using namespace std;
const int N=3e3+1;
inline int read(){char ch=getchar();int f=1;while(ch<'0' || ch>'9') {if(ch=='-') f=-f; ch=getchar();}
int x=0;while(ch>='0' && ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();return x*f;}
int n,a[N];
int f[N][N];
int main(){
n=read();
for(int i=1;i<=n;i++) a[i]=read();
for(int len=1;len<=n;len++) //区间 dp 的套路,为了满足无后效性,需要按照区间大小进行枚举,而不是按照左右端点
for(int i=1;i<=n-len+1;i++) {
int j=i+len-1;
// 如果 len=1 则 f[i][j]=f[i+1][j-1]=0 不影响答案,不必特判
if(a[i]==a[j]) f[i][j]=f[i+1][j-1];
else f[i][j]=min(f[i+1][j],min(f[i][j-1],f[i+1][j-1]))+1;
} printf("%d
",f[1][n]); //最终答案
return 0;
}