题意
- 给你一个数列,你每秒可以从中删去一个回文子串,问你最少多少秒可以删完这个子串
思路
- 其实这类回文dp,方法都是类似的,用dp[i][j]表示从i到j这个子串最少多少秒可以被删完
- 然后我们从中间向两边看更新dp,用dp[i][k]和dp[k+1][j]更新一下dp,再考虑a[i] == a[j]时单独更新一下dp就行了。
- 比如之前做过的最长回文子序列,也是类似的想法
- 具体来说这题,转移,对于一般情况,dp[i][j] = min(dp[i][k] + dp[k+1][j], dp[i][j]),这个不难想到,即先删去左半边,再删去右半边,看怎么删最好。当a[i] == a[j]时,就麻烦一些,我一开始在这也有点犯晕。。其实,我们这么想,这次操作,一定是把中间的某些子串先删去,最后剩下包含a[i]和a[j]的串正好是回文的,然后我们一下就删掉了。那么,dp[i+1][j-1]正好就可以帮助我们来解决问题,因为它也是类似的方法删去它中间的一些子串,剩下一个回文串,再把它删掉,所以我们可以在还剩下一个回文串时,把a[i]和a[j]加到这个串上,它还是回文的,所以我们就得到了转移,dp[i][j] = min(dp[i][j] , dp[i+1][j-1])。
- 这题,我稍微预处理了一下,把找出了其中全部的回文串,让dp = 1。
实现
#include <iostream>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <cstdio>
using namespace std;
const int maxn = 505;
int dp[maxn][maxn];
int a[maxn];
int main(){
int n;
memset(dp,0x3f,sizeof(dp));
cin>>n;
for (int i=1;i<=n;i++)
scanf("%d",&a[i]);
for(int i=1;i<=n;i++)
{
dp[i][i] = 1;
}
for (int len=1;len<n;len++){
for (int i=1;i+len<=n;i++){
int j = i + len;
if (a[i] != a[j]){
continue;
}
if (len == 1 || dp[i+1][j-1] == 1){
dp[i][j] = 1;
}
}
}
for (int len=1;len<n;len++){
for (int i=1;i+len<=n;i++){
int j = i + len;
for (int k=1;k<=len;k++){
dp[i][j] = min(dp[i][j],dp[i+k][j] + dp[i][i+k-1]);
}
if (a[i] == a[j] && dp[i][j] > 1){
dp[i][j] = min(dp[i][j],dp[i+1][j-1]);
}
}
}
cout << dp[1][n] << "\n";
return 0;
}