P3847 [TJOI2007]调整队形 题解
简要题意:
每次可以在数组中插入一个数(可以在两端)或删除一个数,改变一个数;求让数组成为回文数组的最小步数。
这题的蓝太假了,水 \(\text{dp}\),想到算法就可以做出。
关键还是要想到用 动态规划 啊,要是想不到这题就做不出来了。
(考场上可以先用搜索,然后记忆化推导也行,但是这里就不推了)
显然考虑区间 \(\text{dp}\).
用 \(f_{i,j}\) 表示将 \(a_i\) 到 \(a_j\) 变成回文的最小步数。
那么有:
\[ f_{i,j} =
\begin {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 \not = 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\n",f[1][n]); //最终答案
return 0;
}
简易的代码胜过复杂的说教。