hdu5760Palindrome Bo

链接:http://acm.hdu.edu.cn/showproblem.php?pid=5760

题意:给定一个n个元素的数组,求最长的回文子序列并且要求从中间到两边是非递减的,要求输出长度和有多少个本质不同的最长的回文子序列。

分析:如果只是要求长度,这题的难度要小好多,直接用a数组和a的逆数组求最长公共不上升子序列即可,有兴趣的可以自己去找题做做。但是这题还要求输出有多少个本质不同的子序列,我是看题解补的。设dp[l][r]表示a[l]==a[r]的最长长度和对应的方案数,如果我们直接像做普通区间dp那样去枚举的话是O(n^3)的而且也不好去重。题解的方法是预处理出nex[i][j]和pre[i][j]分别表示在第i个位置之后的第一个j在的位置和在第i个位置之前的第一个j在的位置,然后固定dp[l][r]中的l(倒序枚举l),去枚举r,然后我们会发现能够更新的区间一定要是a[l]==a[r]并且当有a[l]>=a[r]我们就更新被继承的子区间now(见代码),那么在更新now的时候我们判断一下是否有相同的子区间被计算过,然后这里就可以去重啦。详见代码。O(n^2)

代码:

#include<map>
#include<set>
#include<cmath>
#include<queue>
#include<bitset>
#include<math.h>
#include<vector>
#include<string>
#include<stdio.h>
#include<cstring>
#include<iostream>
#include<algorithm>
#pragma comment(linker, "/STACK:102400000,102400000")
using namespace std;
const int N=5010;
const int mod=100000000;
const int MOD1=1000000007;
const int MOD2=1000000009;
const double EPS=0.00000001;
typedef long long ll;
const ll MOD=1000000007;
const int MAX=1000000010;
const ll INF=1ll<<55;
const double pi=acos(-1.0);
typedef double db;
typedef unsigned long long ull;
pair<int,int>dp[N][N];
int g,a[N],b[N],nex[N][N],pre[N][N];
void init(int n) {
    int i,j;
    for (i=1;i<=n;i++) scanf("%d", &a[i]),b[i]=a[i];
    sort(b+1,b+n+1);
    g=unique(b+1,b+n+1)-(b+1);
    for (i=1;i<=n;i++) a[i]=lower_bound(b+1,b+g+1,a[i])-b;
    memset(b,0,sizeof(b));
    for (i=1;i<=n+1;b[a[i]]=i,i++)
        for (j=1;j<=g;j++) pre[i][j]=b[j];
    memset(b,0,sizeof(b));
    for (i=n;i>=0;b[a[i]]=i,i--)
        for (j=1;j<=g;j++) nex[i][j]=b[j];
}
int main()
{
    int i,j,n,l,r;
    pair<int,int>now;
    while (scanf("%d", &n)!=EOF) {
        init(n);
        for (i=n;i;i--) {
            dp[i][i]=make_pair(1,1);
            now=make_pair(0,1);
            for (j=i+1;j<=n;j++) {
                dp[i][j]=make_pair(0,0);
                if (a[i]==a[j]) dp[i][j]=make_pair(now.first+2,now.second);
                if (a[i]>=a[j]) {
                    l=nex[i][a[j]];r=pre[j][a[j]];
                    if (dp[l][j].first>now.first) now=dp[l][j];
                    else if (dp[l][j].first==now.first) {
                            if (r>=l&&dp[l][r].first==dp[l][j].first) now.second-=dp[l][r].second;
                            if (now.second<0) now.second+=MOD1;
                            now.second=(now.second+dp[l][j].second)%MOD1;
                        }
                }
            }
        }
        now=make_pair(0,0);
        for (i=1;i<=g;i++) {
            l=nex[0][i];r=pre[n+1][i];
            if (l==0||r==0) continue ;
            if (now.first<dp[l][r].first) now=dp[l][r];
            else if (now.first==dp[l][r].first) now.second=(now.second+dp[l][r].second)%MOD1;
        }
        printf("%d %d\n", now.first, (now.second+MOD1)%MOD1);
    }
    return 0;
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值