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;  
}
posted @ 2021-02-13 21:36  Paranoid5  阅读(54)  评论(0编辑  收藏  举报