[51nod1324] 相似序列对

该博客探讨了一道数学问题,涉及寻找长度为N的序列对(A, B),要求从A删除0到2个元素,并在B中删除相应数量的元素,使两个序列相同。问题约束是序列元素在1到M的范围内,且N≤100,M≤1000000000。解决方案聚焦于计算最长公共子序列的策略,提出了一个时间复杂度为n^2的初步思路。" 88371467,5652164,Ubuntu 18.04 安装 Docker 使用 deb 包,"['Docker', 'Linux', 'Ubuntu']

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目大意

给定N,M,问有多少不同的序列对(A,B)满足:
1. 长度均为N
2. 从A中删去0到2个元素,在B中删去相同数量的数,然后两个序列相同
3. 序列都是[1,M]内的整数

答案对109+9取模,N≤100,1≤M≤1000000000

分析

第2个条件相当于两个序列的最长公共子序列长度不小于n-2
首先考虑对于两个序列,如何求它们的最长公共子序列。
有一个很清晰的n2的方法:设f[i][j]表示A的前i个,B的前j个,求出最长公共子序列的长度。容易得到:

f[i][j]={f[i1][j1]+1min(f[i][j1],f[i1][j])A[i]=B[j]A[i]≠B[j]

现在用这个DP的思路来构造序列。
设F[i][S]表示确定了两个序列的前i位,S表示最后三位(i-2,i-1,i)数的状态以及f[][]数组最后三位的状态。然后枚举第i+1位的两个数,然后更新f数组,最后存入新状态。具体细节就不说了。
最后三位的状态用最小表示法,最终搜出来状态数很少,可以通过此题。
注意把n=1、2的情况特判掉
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <map>

#define max(a,b) ((a)>(b)?(a):(b))

using namespace std;

const int mo=1e9+9,N=1005;

typedef long long LL;

int n,B,m,p,q,f[2][N];

LL st[2][N],H;

int a[2][4],b[20],c[20],dp[4][4],id[4][4],tot[2];

map <LL,int> h[2];

int v[7];

void init(int x,int y,int s)
{
    if (x>6)
    {
        if (y>m || y>5) return;
        for (int i=1;i<4;i++)
        {
            a[0][i]=b[i*2-1]; a[1][i]=b[i*2];
        }
        dp[0][0]=0;
        for (int i=1;i<=3;i++)
        {
            for (int j=1;j<=3;j++)
            {
                if (a[0][i]==a[1][j]) dp[i][j]=dp[i-1][j-1]+1;
                else dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
                b[id[i][j]]=3-dp[i][j];
            }
        }
        if (dp[3][3]==0) return;
        H=0;
        for (int i=3;i<=15;i++) H=H*6+b[i];
        if (h[0][H]==0)
        {
            h[0][H]=++tot[0];
            st[0][tot[0]]=H; f[0][tot[0]]=0;
        }
        f[0][h[0][H]]=(f[0][h[0][H]]+s)%mo;
        return;
    }
    for (b[x]=1;b[x]<=y;b[x]++) init(x+1,y,s);
    init(x+1,b[x],(LL)s*(m-y)%mo);
}

void extend(int x,int y,int s,int i)
{
    if (x>6)
    {
        if (y>5 || y>m) return;
        a[0][3]=b[5]; a[1][3]=b[6];
        if (a[0][1]==a[1][3]) dp[1][3]=dp[0][2]+1;else dp[1][3]=max(dp[0][3],dp[1][2]);
        if (a[0][2]==a[1][3]) dp[2][3]=dp[1][2]+1;else dp[2][3]=max(dp[1][3],dp[2][2]);
        if (a[0][3]==a[1][1]) dp[3][1]=dp[2][0]+1;else dp[3][1]=max(dp[3][0],dp[2][1]);
        if (a[0][3]==a[1][2]) dp[3][2]=dp[2][1]+1;else dp[3][2]=max(dp[3][1],dp[2][2]);
        if (a[0][3]==a[1][3]) dp[3][3]=dp[2][2]+1;else dp[3][3]=max(dp[2][3],dp[3][2]);
        if (dp[3][3]<i-2) return;
        b[id[1][3]]=i-dp[1][3]; b[id[2][3]]=i-dp[2][3]; b[id[3][1]]=i-dp[3][1]; b[id[3][2]]=i-dp[3][2]; b[id[3][3]]=i-dp[3][3];
        H=0;
        for (int i=3;i<=15;i++) H=H*6+b[i];
        if (h[q][H]==0)
        {
            h[q][H]=++tot[q];
            st[q][tot[q]]=H; f[q][tot[q]]=0;
        }
        f[q][h[q][H]]=(f[q][h[q][H]]+s)%mo;
        return;
    }
    for (b[x]=1;b[x]<=y;b[x]++) extend(x+1,y,s,i);
    extend(x+1,b[x],(LL)s*(m-y)%mo,i);
}

int main()
{
    scanf("%d%d",&n,&m);
    if (n==1)
    {
        printf("%d\n",(LL)m*m%mo); return 0;
    }
    if (n==2)
    {
        printf("%d\n",(LL)m*m%mo*m%mo*m%mo); return 0;
    }
    id[1][1]=7; id[1][2]=8; id[1][3]=9;
    id[2][1]=10; id[2][2]=11; id[2][3]=12;
    id[3][1]=13; id[3][2]=14; id[3][3]=15;
    init(1,0,1);
    p=0; q=1;
    for (int i=4;i<=n;i++,p^=1,q^=1)
    {
        tot[q]=0;
        h[q].clear();
        for (int j=1;j<=tot[p];j++)
        {
            H=st[p][j];
            int s=f[p][j];
            for (int k=15;k>2;k--,H/=6) c[k]=H%6;
            int cnt=0;
            memset(v,0,sizeof(v));
            for (int k=1;k<5;k++)
                if (v[c[k+2]]>0) a[(k&1)^1][(k+1)>>1]=b[k]=v[c[k+2]];else a[(k&1)^1][(k+1)>>1]=b[k]=v[c[k+2]]=++cnt;
            for (int p=1;p<3;p++) for (int q=1;q<3;q++)
            {
                dp[p][q]=i-1-c[id[p+1][q+1]]; b[id[p][q]]=i-dp[p][q];
            }
            dp[0][0]=i-1-c[id[1][1]]; dp[0][1]=i-1-c[id[1][2]]; dp[0][3]=dp[0][2]=i-1-c[id[1][3]]; dp[1][0]=i-1-c[id[2][1]]; dp[3][0]=dp[2][0]=i-1-c[id[3][1]];
            extend(5,cnt,s,i);
        }
    }
    int ans=0;
    for (int i=1;i<=tot[p];i++) ans=(ans+f[p][i])%mo;
    printf("%d\n",ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值