Codeforces 335B Palindrome 鸽巢原理 + DP

题目大意:

就是现在给出一个长度不超过5*10^4的串, 求其最长回文串, 如果回文串长度超过100只需要输出长度为100的即可, 输入的字符串只包含26种小写英文字母

另外就是这个题目中的回文串并不要求在原来的串当中是连续出现的, 只需要出现的位置顺序一样即可, 并不要求连续


大致思路:

做练习的时候一眼看标题课描述没看Hint就脑补直接当常见的的连续的回文串来做了....上来就敲了一发后缀数组结果发现样例没过然后看错题了啊....

现在重新做了一下, 发现是一个可以二维复杂度O(n*n)的dp, 因为最多只有26个字母, 当字符串长度超过26*99 + 1时一定会有某个字符出现了100次以上, 输出这个字符100次即可

所以只需要对于长度不超过大约2600一下的字符串进行dp求解即可

DP状态转移方程和路径回溯见代码


代码如下:

Result  :  Accepted     Memory  :  79432 KB     Time  :  186 ms

/*
 * Author: Gatevin
 * Created Time:  2015/3/3 17:45:08
 * File Name: Shana.cpp
 */
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
const double eps(1e-8);
typedef long long lint;

/*
 * 用dp[i][j]表示位置i和j之间的串能找到的最长的回文串(该题定义的版本)的长度
 * 那么不难发现转移方程当s[i] == s[j]时 dp[i][j] = dp[i + 1][j - 1] + 2 (Greedy)
 * 否则dp[i][j] = max(dp[i + 1][j], dp[i][j - 1])
 * 然后我们用一个last[l][r]表示dp[l][r]的来源上一个状态是dp[last[l][r].first][last[l][r].second]
 * 用last数组回溯就可以找到原来的串, 注意回溯的时候L和R变化至s[L] == s[R] && R == L + 1的时候回文串是偶数长度
 * 这个dp是O(n*n)的
 * 但是由于本题只有26个字母, 当长度达到25*99 + 1时就必然会出现某个字符出现了至少100次(鸽巢原理)
 * 所以只需要对长度 < 2600的进行dp求解就足够了
 */

int dp[2600][2600];
pair last[2600][2600];
int n;
char s[50010];
int cnt[30];
vector v;

int main()
{
    scanf("%s", s);
    n = strlen(s);
    if(n >= 2600)
    {
        for(int i = 0; i < n; i++)
        {
            cnt[s[i] - 'a']++;
            if(cnt[s[i] - 'a'] >= 100)
            {
                for(int j = 0; j < 100; j++)
                    printf("%c", s[i]);
                return 0;
            }
        }
    }
    for(int i = 0; i < n; i++) dp[i][i] = 1;
    for(int L = 2; L <= n; L++)
        for(int i = 0; i + L - 1 < n; i++)
        {
            if(s[i] == s[i + L - 1])
            {
                dp[i][i + L - 1] = dp[i + 1][i + L - 2] + 2;
                last[i][i + L - 1] = make_pair(i + 1, i + L - 2);//记录最佳状态的来源
            }
            else
            {
                if(dp[i + 1][i + L - 1] > dp[i][i + L - 2])
                {
                    dp[i][i + L - 1] = dp[i + 1][i + L - 1];
                    last[i][i + L - 1] = make_pair(i + 1, i + L - 1);
                }
                else
                {
                    dp[i][i + L - 1] = dp[i][i + L - 2];
                    last[i][i + L - 1] = make_pair(i, i + L - 2);
                }
            }
        }
    int L = 0, R = n - 1;
    bool even = false;
    while(L < R)
    {
        if(s[L] == s[R])
        {
            v.push_back(s[L]);
            if(L + 1 == R)
            {
                even = true;
                break;
            }
        }
        int t = last[L][R].first;
        R = last[L][R].second;
        L = t;
    }
    for(unsigned int i = 0; i < min(v.size(), 50u); i++)
        printf("%c", v[i]);
    if(!even && v.size() < 50) printf("%c", s[L]);//判断中间是否有一个字符出现一次
    for(int i = min((int)v.size() - 1, 49); i >= 0; i--)
        printf("%c", v[i]);
    return 0;
}


你可能感兴趣的:(Codeforces)