P3624 DNA - dp求字典序

P3624 DNA - dp

P3624 [APIO2008] DNA

题意

这段ai写的洛谷没过

  • DNA 序列由 ACGT 四种字符组成,其中 N 表示是 A/C/G/T 中的任意一个。
  • 四种核苷酸的优先级顺序:A > C > G > T
  • 范式-1:序列中每个核苷酸都 ≥ 其右边的核苷酸(按优先级比较)。
  • 范式-j(j>1):要么是范式-(j-1),要么是范式-(j-1) 和范式-1 的连接。

任务:给定一个包含 N 的未完成序列,找出所有适合该序列的范式-K DNA 序列中,优先级排序的第 R 个。

输入:

  • 第一行:M K R (序列长度,范式等级 K,要找的排名 R
  • 第二行:长度为 M 的字符串(包含 A/C/G/T/N)

输出:

  • R 个适合该序列的范式 -K DNA 序列

思路

显然我们只要知道在每个 \(N\) 的位置知道其所有选择的对应的方案数,通过不断确定状态,来限定其后面的最低排名,就可以得到答案。

不过想要知道其后面的方案数,显然要从后面向前计算。如果作的是 \(N\) 前面的方案数,并最后从后向前推的时候,会发现限制出来的 DNA 序列不是按优先级升序的,所以只能从后向前计算

考虑 \(dp\),容易想到开一维存位置,然后由于范式 \(-1\) 对顺序有要求,并且 \(N\)\(4\) 种选择,所以还要开一维表示当前的字母,我们要求的是范式 \(-K\) 的方案数,而 \(-K\) 的方案数显然可以由 \(-(K-1)\) 推得,所以再开一维表示当前的范式等级。即:

\(dp_{i,j,b}\) 表示第 \(i\) 个位置,当前范式等级为 \(j\),当前字母为 \(b\) 的方案数。

思考如何转移:

  1. 如果当前是 \(N\),那么字母需要枚举,否则 \(b\) 是确定的。
  2. 范式等级需要枚举到 \(K\),发现如果当前优先级和后面连上了,那当前等级 \(j\) 可由 \(i+1\)\(j\) 推得,否则由 \(i+1\)\(j-1\) 推得,即当前 \(j\)\(j-(b>bb)\) 推得
  3. 所有比 \(K\) 小的等级也属于范式 \(-K\) 的方案数,这里作前缀和优化。

最后得到:

\[dp_{i,j,b} = \sum_{bb=1}^4 {dp_{i+1,j-(b>bb),bb}} \]

代码

#include <bits/stdc++.h>
#define int long long
using namespace std;
constexpr int maxn = 1e5+10;

int n,k,r;
int wi[maxn],dp[maxn][15][5];

int c2i(char c)
{
    if(c=='A') return 1;
    if(c=='C') return 2;
    if(c=='G') return 3;
    if(c=='T') return 4;
    return 0;
}

char i2c(int x)
{
    if(x==1) return 'A';
    if(x==2) return 'C';
    if(x==3) return 'G';
    return 'T';
}

signed main()
{
    scanf("%lld%lld%lld",&n,&k,&r);
    for(int i=1;i<=n;++i)
    {
        char c;
        scanf(" %c",&c);
        wi[i]=c2i(c);
    }

    if(!wi[n])
    {
        for(int i=1;i<=4;++i)
        {
            dp[n][1][i]=1;
        }
    }
    else
    {
        dp[n][1][wi[n]]=1;
    }

    for(int i=n-1;i>=1;--i)
    {
        if(wi[i])
        {
            for(int j=1;j<=k;++j)
            {
                for(int bb=1;bb<=4;++bb)
                {
                    dp[i][j][wi[i]]+=dp[i+1][j-(wi[i]>bb)][bb];
                }
            }
        }
        else
        {
            for(int b=1;b<=4;++b)
            {
                for(int j=1;j<=k;++j)
                {
                    for(int bb=1;bb<=4;++bb)
                    {
                        dp[i][j][b]+=dp[i+1][j-(b>bb)][bb];
                    }
                }
            }
        }
    }

    for(int i=1;i<=n;++i)
    {
        for(int j=1;j<=k;++j)
        {
            for(int b=1;b<=4;++b)
            {
                dp[i][j][b]+=dp[i][j-1][b];
            }
        }
    }

    int lst=0;
    for(int i=1;i<=n;++i)
    {
        if(wi[i])
        {
            printf("%c",i2c(wi[i]));
            k-=(lst>wi[i]);
            lst=wi[i];
        }
        else
        {
            int b=1;
            for(;b<=4 && r>dp[i][k-(lst>b)][b];++b)
            {
                r-=dp[i][k-(lst>b)][b];
            }
            k-=(lst>b);
            lst=b;
            printf("%c",i2c(b));
        }
    }

    return 0;
}
posted @ 2025-11-10 14:07  玖玮  阅读(4)  评论(0)    收藏  举报