【JSOI2016】扭动的回文串

内存限制:256 MiB 时间限制:1000 ms

题目描述

JYY 有两个长度均为 N 的字符串 A 和 B。
一个「扭动字符串」S(i,j,k)S(i,j,k)S(i,j,k) 由 A 中的第 i 个字符到第 j 个字符组成的子串与 B 中的第 j个字符到第 k 个字符组成的子串拼接而成。 比如,若 A=’XYZ’,B=’UVW’,则扭动字符串S(1,2,3)=S(1,2,3)=S(1,2,3)=’XYVW’。
JYY 定义一个「扭动的回文串」为如下情况中的一个:
1. A 中的一个回文串;
2.B 中的一个回文串;
3.或者某一个回文的扭动字符串S(i,j,k)S(i,j,k)S(i,j,k)
现在 JYY希望找出最长的扭动回文串。

输入格式

第一行包含一个正整数 N。
第二行包含一个长度为 N 的由大写字母组成的字符串 A。
第三行包含一个长度为 N 的由大写字母组成的字符串B。

输出格式

输出的第一行一个整数,表示最长的扭动回文串。

样例输入

5
ABCDE
BAECB

样例输出

5

样例解释

最佳方案中的扭动回文串如下所示(不在回文串中的字符用 . 表示):
.BC..
..ECB

数据范围与提示

对于所有的数据, 1N105 1 ≤ N ≤ 10 5

题解

A,B A , B 两串上的回文串不管用 manacher m a n a c h e r 还是回文自动机都很好处理,关键在于解决“扭动字符串”的情况。我们考虑讨论回文串上最特殊的点——回文串的中点属于哪一个串。记讨论的回文中心为点 k k ,当k A A 串上时,”扭动字符串”的拐点一定在A串上以 k k 为中心形成的最长回文串的右端点;同理,当k B B 串上时,”扭动字符串”的拐点一定在B串上以 k k 为中心形成的最长回文串的左端点。因此,我们只需枚举k的位置,并用 manacher m a n a c h e r 算法算出左右端点(一般与回文串中点有关的题,多会用到 manacher m a n a c h e r ),最后用二分法枚举最长能延伸的长度(回文串长度满足二分性质),并用 hash h a s h 检验即可( O(1) O ( 1 ) 检验回文串的重要方法)。

代码

#include<stdio.h>
#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<cmath>
typedef long long ll;
using namespace std;
const int maxn=2e5+5;
const ll mod=1e9+7;
const ll seed=131;
ll n,ans,mi[maxn],inv[maxn];
struct node
{
    ll len,pos,maxx,rad[maxn];
    ll val1[maxn],val2[maxn];
    char c[maxn],temp[maxn];
    void init()
    {
        maxx=len=pos=0,c[++len]='$',val1[0]=val2[0]=0;
        for(ll i=1;i<=n;i++) c[++len]=temp[i],c[++len]='$';
    }
    void manacher()
    {
        for(ll i=1;i<=len;i++)
        {
            if(i>maxx) rad[i]=1;
            else rad[i]=min(rad[pos*2-i],maxx-i+1);
            while(i-rad[i]>0&&i+rad[i]<=len&&c[i-rad[i]]==c[i+rad[i]]) rad[i]++;
            if(i+rad[i]-1>maxx) maxx=i+rad[i]-1,pos=i;
            if(rad[i]>ans) ans=rad[i];
        }
    }
    void Hash()
    {
        for(ll i=1;i<=len;i++)
        {
            val1[i]=(val1[i-1]+c[i]*mi[i])%mod;
            val2[i]=(val2[i-1]+c[i]*inv[i])%mod;
        }
    }
}A,B;
ll ksm(ll x,ll y)
{
    ll temp=1;
    while(y)
    {
        if(y&1) temp=temp*x%mod;
        y>>=1,x=x*x%mod;
    }
    return temp;
}
bool check1(ll x,ll y)
{
    ll x1=x-A.rad[x],x2=x1-y+1;
    ll num1=(A.val1[x1]-A.val1[x2-1]+mod)*inv[x2]%mod;
    x1=x+A.rad[x]-2,x2=x1+y-1;
    ll num2=(B.val2[x2]-B.val2[x1-1]+mod)*mi[x2]%mod;
    return num1==num2;
}
bool check2(ll x,ll y)
{
    ll x1=x+B.rad[x],x2=x1+y-1;
    ll num1=(B.val2[x2]-B.val2[x1-1]+mod)*mi[x2]%mod;
    x1=x-B.rad[x]+2,x2=x1-y+1;
    ll num2=(A.val1[x1]-A.val1[x2-1]+mod)*inv[x2]%mod;
    return num1==num2;
}
int main()
{
    scanf("%lld%s%s",&n,A.temp+1,B.temp+1),A.init(),B.init();
    A.manacher(),B.manacher();
    mi[0]=inv[0]=1,mi[1]=seed,inv[1]=ksm(seed,mod-2);
    for(ll i=2;i<=A.len;i++)
    {
        mi[i]=mi[i-1]*mi[1]%mod;
        inv[i]=inv[i-1]*inv[1]%mod;
    }
    A.Hash(),B.Hash();
    for(ll i=1;i<=A.len;i++)
    {
        ll l=0,r=min(i-A.rad[i],A.len-(i+A.rad[i])+1);
        while(l<=r)
        {
            ll mid=(l+r)>>1;
            if(check1(i,mid)) l=mid+1;
            else r=mid-1;
        }
        ans=max(ans,A.rad[i]+r);
    }
    for(ll i=1;i<=B.len;i++)
    {
        ll l=0,r=min(i-B.rad[i],B.len-(i+B.rad[i])+1);
        while(l<=r)
        {
            ll mid=(l+r)>>1;
            if(check2(i,mid)) l=mid+1;
            else r=mid-1;
        }
        ans=max(ans,B.rad[i]+r);
    }
    printf("%lld\n",ans-1);
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
根据引用[1],dp[u][j]表示在u子树选取恰好j个人时能获得的最大价值。而根据引用,该问题的时间复杂度为O(log2​104×nm)。 对于洛谷P2143 [JSOI2010] 巨额奖金问题,我们可以使用动态规划来解决。具体步骤如下: 1. 首先,我们需要构建一棵树来表示员工之间的关系。树的根节点表示公司的总经理,其他节点表示员工。每个节点都有一个权值,表示该员工的奖金金额。 2. 接下来,我们可以使用动态规划来计算每个节点的dp值。对于每个节点u,我们可以考虑两种情况: - 如果选择节点u,则dp[u][j] = dp[v][j-1] + value[u],其v是u的子节点,value[u]表示节点u的奖金金额。 - 如果不选择节点u,则dp[u][j] = max(dp[v][j]),其v是u的子节点。 3. 最后,我们可以通过遍历树的所有节点,计算出dp[u][j]的最大值,即为所求的巨额奖金。 下面是一个示例代码,演示了如何使用动态规划来解决洛谷P2143 [JSOI2010] 巨额奖金问题: ```python # 构建树的数据结构 class Node: def __init__(self, value): self.value = value self.children = [] # 动态规划求解最大奖金 def max_bonus(root, j): dp = [[0] * (j+1) for _ in range(len(root)+1)] def dfs(node): if not node: return for child in node.children: dfs(child) for k in range(j, 0, -1): dp[node.value][k] = max(dp[node.value][k], dp[node.value][k-1] + node.value) for child in node.children: for k in range(j, 0, -1): for l in range(k-1, -1, -1): dp[node.value][k] = max(dp[node.value][k], dp[node.value][k-l-1] + dp[child.value][l]) dfs(root) return dp[root.value][j] # 构建树 root = Node(1) root.children.append(Node(2)) root.children.append(Node(3)) root.children[0].children.append(Node(4)) root.children[0].children.append(Node(5)) root.children[1].children.append(Node(6)) # 求解最大奖金 j = 3 max_bonus_value = max_bonus(root, j) print("最大奖金为:", max_bonus_value) ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值