hihocoder 1149 回文字符序列(区间dp)

题意

时间限制:2000ms
单点时限:1000ms
内存限制:256MB

描述

给定字符串,求它的回文子序列个数。回文子序列反转字符顺序后仍然与原序列相同。例如字符串aba中,回文子序列为”a”, “a”, “aa”, “b”,
“aba”,共5个。内容相同位置不同的子序列算不同的子序列。

输入

第一行一个整数T,表示数据组数。之后是T组数据,每组数据为一行字符串。

输出

对于每组数据输出一行,格式为”Case #X: Y”,X代表数据编号(从1开始),Y为答案。答案对100007取模。

数据范围
1 ≤ T ≤ 30

小数据

字符串长度 ≤ 25

大数据

字符串长度 ≤ 1000

样例输入

5
aba
abcbaddabcba
12111112351121
ccccccc
fdadfa

样例输出

Case #1: 5
Case #2: 277
Case #3: 1333
Case #4: 127
Case #5: 17

思路

典型的区间dp.

if(s[i] == s[j])
    dp[i][j] = dp[i+1][j]+dp[i][j-1]+1;
else
    dp[i][j] = dp[i+1][j]+dp[i][j-1]-dp[i+1][j-1];

简单解释一下,先说一下s[i] != s[j]时,dp[i+1][j]+dp[i][j-1]然后再减去重复的dp[i+1][j-1]部分,这个不难理解。
那么当s[i] == s[j]时,显然i与j之间所有是回文串的子序列可以用s[i],s[j]包裹成为新的子序列,所以重复部分不需要剪去了,还需要加上1是因为
s[i]s[j]两个单独一起也可以作为一个回文子序列。

代码

#include <cstdio>
#include <iostream>
#include <cstdlib>
#include <cstring>

using namespace std;

const int MOD = 100007;
char s[1009];
int dp[1009][1009];

int main()
{
    int T;
    scanf("%d", &T);
    for(int i=1; i<=T; i++)
    {
        memset(dp, 0, sizeof(dp));
        scanf("%s", s+1);
        int n = strlen(s+1);
        for(int l=0; l<n; l++)
        {
            for(int i=1; i+l<=n; i++)
            {
                int j = i+l;
                if(s[i] == s[j])
                    dp[i][j] = (dp[i+1][j]+dp[i][j-1]+1)%MOD;
                else
                    dp[i][j] = (dp[i+1][j]+dp[i][j-1]-dp[i+1][j-1]+MOD)%MOD;
            }
        }
        printf("Case #%d: %d\n", i, dp[1][n]);
    }
    return 0;
}