HDU 4745 Two Rabbits (区间dp 求最长回文子序列的长度)

题目在这里

看了一些大佬的题解,写写自己的理解加深一下印象,是第一篇博客有点小激动。小白写的不好勿喷~

题目:

n个石块按照编号顺时针排成一圈,两只兔子从各自的起点(可相同)出发,
一只按照顺时针方向跳跃,另一只按照逆时针方向
保证两只兔子每次落脚的石块权值ai是相同的,两只兔子可以同时落脚于同一石块 
【两只兔子都不能跃过各自的起点】 
求落脚次数的最大值 
(1 <= n <= 1000, 1 <= ai <= 1000)


解析:

1、首先因为是环,所以可以考虑倍增成链,存到数组s中,
     其实就是求倍增后的链中最长回文子序列的长度。
2、考虑区间dp,dp[i][j]:=从第i个石块到第j个石块范围内最长回文子序列的长度
3、状态方程 
     注意--一个数值也回文,所以dp[i][i]=1
     若i<j && s[i]=s[j] 则dp[i][j]=dp[i+1][j-1]+2
     若i<j && s[i]!=s[j] 则dp[i][j]=max( dp[i][j-1],dp[i+1][j] )


注意:

1、给dp数组赋值时一维二维的循环顺序。
   由状态方程可知dp[i][j]由i+1的状态和j-1的状态决定
   所以第一维要逆序循环,第二维要顺序循环,且i<j
   即: 
   for(int j=1;j<2*n;j++) 
    for(int i=j-1;i>=0;i--)
2、 dp[0][2*n-1]并不是最终答案,考虑以下两点:
(1)题目要求跳跃过程不能越过起点,所以求得的最长回文子序列所在的区间长度不能超过n,
需要遍历长度为n的区间求最长回文子序列长度最大值。
可以想象成设置了一个长度为n的窗口。
即: 
int ans = 0;  
            for (int i = 0; i < n; i++)  
            ans = max(ans, dp[i][i + n - 1]); //窗口为n的长度的最大值 
(2) 上面求出的ans也不是最终答案。因为上面没有考虑到,当窗口n中的最长回文子序列为 
          x1,x2,...xp,且p<n时,【可能】存在xk ( xk不在x1~xp中,但在s[i]~s[i+n-1]中 
  且xk在数组s中的下标大于x1~xp中任意元素在数组s中的下标 。即沿该子序列跳跃时,没有跨过xk。),
  使得x1,x2,...,xp,xk 和 xp,xp-1,...,x2,x1,xk两个数列相同。所以此时答案为p+1。
  但是我们在求最长回文子序列的长度时,并没有标记回文子序列最后一个元素的下标, 
  所以要判断是否存在xk有些困难。
  不如转换一种思路,遍历长度为n-1的区间内最长回文子序列的长度,求出最大值,
  再加上1 ( 即为xk,这时就【一定】存在xk了 )。得出的结果再与上面循环得出的ans
  进行比较,二者中最大值即为最终结果。
  即先执行上面的循环,再执行下面的:
  for (int i = 0; i < n; i++)  
                ans = max(ans, dp[i][i + n - 2] + 1);  //窗口为n-1的长度的最大值+1 

                  printf("%d\n", ans); 


AC代码如下    
#include <iostream>
#include <stdio.h>
#include <algorithm>
#include <cstring>
#include <cmath>
using namespace std;
int n;
int s[2007];	
int dp[2007][2007];

void solve()
{
	memset(dp,0,sizeof(dp));
	int len=2*n;
	for(int i=0;i<len;i++)
		dp[i][i]=1;
	for(int j=1;j<len;j++)
	{
		for(int i=j-1;i>=0;i--)
		{
			if(s[i]==s[j])
				dp[i][j]=dp[i+1][j-1]+2;
			else
				dp[i][j]=max(dp[i+1][j],dp[i][j-1]);
		}
	} 
	int ans = 0;  
    for (int i = 0; i < n; i++)  
    	ans = max(ans, dp[i][i + n - 1]); //窗口为n的长度的最大值 	
    for (int i = 0; i < n; i++)  
        ans = max(ans, dp[i][i + n - 2] + 1);  //窗口为n-1的长度的最大值+1 
    printf("%d\n", ans); 
}

int main() {
	//freopen("1.txt","r",stdin);
	while(~scanf("%d",&n) && n!=0)
	{
		getchar();
		for(int i=0;i<n;i++)
		{
			scanf("%d",&s[i]);
			s[i+n]=s[i];
		}
		solve();
	}
	return 0;
}

猜你喜欢

转载自blog.csdn.net/byn12345/article/details/79417119