题目在这里
看了一些大佬的题解,写写自己的理解加深一下印象,是第一篇博客有点小激动。小白写的不好勿喷~
题目:
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);
#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;
}