POJ1159
POJ1159
求一个字符串s变成回文字符串最少需要多少个字符。
1.从后往前推
dp[l][r]表示从l到r会有最少需要多少个字符
那么最终的答案应该是 dp[0][n-1]。
状态转移方程应该是:
dp[l] == dp[r] : dp[l][r] = dp[l+1][r-1]//因为前后两端相同不会影响
dp[l] != dp[r] : dp[l][r] = min{dp[l+1][r],dp[l][r-1]}+1//从左右两端剪掉不断选择最小值
2.从前往后推
定义a[]是s的正序,b[]是s[]的逆序,那么答案就是 n-a[]和b[]的最大公共子序列 。
对照正推和反推其实发现这两者是一样的。本质上逆推求的就是a[]和b[]之间有多少不同的字符(这里不太知道怎么表达,但是可以从方程上面看出来,这两者是十分相似的)。
3.滚动数组优化
从上面可以知道我们使用 dp[5001][5001]的数组可以解决问题。
但是这个问题真正麻烦的就是空间复杂度。
如果使用滚动数组可以解决这个问题(但是记忆化搜索的方法不能这样做)。
我们打表或者观察可以发现,答案都在最后一排,也可以从dp方程里面看出来。
所以我们有用的只有当前这一排,以及求解这一排时需要使用的上一排。
这样就可以变成 dp[2][5001]
最后的话也可以使用short int解决(记忆化搜索时好像只能这么处理)。
code:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <queue>
#include <vector>
#include <deque>
#include <bitset>
using namespace std;
#define lowbit(x) x&-x
#define ll long long
#define dob double
#define For(i,s,n) for(ll i = s;i <= n;i++)
#define mem0(a) memset(a,0,sizeof a)
#define gcd(a,b) __gcd(a,b)
const int N = 5e3+600;
const double eps = 1e-6;
const ll mod = 1e9+7;
const int inf = 0x3f3f3f3f;
int n;
short int dp[N][N] = {0};
char a[N],b[N];
int main() {
ios::sync_with_stdio(false);
cout.tie(NULL);
while(cin>>n){
mem0(dp);
For(i,1,n){
char c;
cin>>c;
a[i] = b[n-i+1] = c;
}
mem0(dp);
For(i,1,n){
For(j,1,n) {
if (a[i] == b[j]) dp[i%2][j] = dp[(i-1)%2][j-1]+1;
else dp[i%2][j] = max(dp[i%2][j-1],dp[(i-1)%2][j]);
}
}
cout<<n-dp[n%2][n]<<endl;
}
return 0;
}