组合数+dp

本文探讨了如何计算给定小写字母字符串T中,长度为m(m≤10^5)的子序列S的数量,要求找到唯一对应新串下标的匹配规则。通过动态规划方法,利用递推公式确保子序列的独特性和匹配规则,解决了寻找满足条件的子序列计数问题。
  • 给出一个小写字母字符串 T,长度为 nnn。
  • 求有多少长度为 m(m≤105)m (m\leq10^5)m(m≤105) 的小写字母字符串 S,满足 T 是 S 的一个子序列。
  • 我们从合法的新串下手,考虑制定出一种方法来从中找出原串。

  • 对这种方法的要求是:按照这种方法 找出的新串 的下标 必须唯一。

  • 一种合理的匹配方式是:

    • 假如原串是:abcabcabc
    • 假如新串是:aaabbbcccaaabbbccaaccaaabbbcccaaabbbccaaccaaabbbcccaaabbbccaacc
    • 匹配应该是:aaabbbcccaa[a]bb[b]ccaac[c]aaabbbcccaa[a]bb[b]ccaac[c]aaabbbcccaa[a]bb[b]ccaac[c]
    • 匹配规则是:对于某个被匹配到的字符 chchch 开始,从它右边第一个开始,到它右边下一个被匹配的字符左边第一个为止,不得出现跟 chchch 相同的字符。
    • 能发现,按照这种方法 找出的新串 的下标是唯一的。
    • 总结一下这种匹配方式:
      • 我们知道,由子序列 SSS 构造原序列 TTT,问方案数这类问题,就是要保证,
      • 如果你在 TTT 确定 TTT 的第 iii 个字符 tit_iti​,匹配的是 SSS 中的第 ppp 个字符 sps_psp​,
      • 并且你在 TTT 确定 TTT 的第 jjj 个字符 tjt_jtj​,匹配的是 SSS 中的第 p+1p+1p+1 个字符 sp+1s_{p+1}sp+1​,
      • 除了 ti=spt_i=s_pti​=sp​,tj=sp+1t_j=s_{p+1}tj​=sp+1​ 以外,还要保证,ti+1...tj−1t_{i+1}...t_{j-1}ti+1​...tj−1​ 的所有字符,都不包含 tit_iti​。
  • 现在考虑按照这种匹配思路,从原串开始,构造新串。

  • void Sol()
    {
        dp[0][0] = 1;
        for (int i=1; i<=m; i++)    //从你决定开始匹配原串第一个字符之前,放什么都行,每一个位置有26种放法。
        {
            dp[i][0] = dp[i-1][0] * 26 % MOD;
        }
         
        for (int i=1; i<=m; i++)//枚举新串
        {
            for (int j=1; j<=min(i, n); j++)//枚举原串
            {
                //对于新串的第 i 个字符,有两种情况:
                //一种是第i个字符匹配上原串的第j个字符
                //一种是第i个字符在前i-1个已经匹配上原串的第j个字符的情况下,有25种放法。
                dp[i][j] += (j-1<0?0:dp[i-1][j-1]) + dp[i-1][j] * 25 % MOD, dp[i][j] %= MOD;
            }
        }
        printf("%lld\n",dp[m][n]);

    链接:登录—专业IT笔试面试备考平台_牛客网
    来源:牛客网

    Link doesn't remember bbb, so he wonders the number of possible sequences bbb.
     

    A bracket sequence is valid if it satisfies any of the following conditions:

  • Its length is 000.
  • It can be represented as (A)(A)(A), where AAA is a valid bracket sequences.
  • It can be represented as ABABAB, where AAA and BBB are both valid bracket sequence.

  • A sequence aaa is a subsequence of a sequence bbb if aaa can be obtained from bbb by deletion of several (possibly, zero or all) elements.

  • #include<iostream>
    #include<cstring>
    #include<cstdio>
    #include<string>
    using namespace std;
    typedef long long ll;
    const int N=205,mod=1e9+7;
    ll dp[N][N][N];
    void solve()
    {
        int n,m;cin>>n>>m;
        string s;
        cin>>s;
        for(int i=0;i<=n;i++)
        {
            for(int j=0;j<=m;j++)
            {
                for(int k=0;k<=m;k++) dp[i][j][k]=0;
            }
        }
        dp[0][0][0]=1;
        for(int i=1;i<=m;i++)
        {
            for(int j=0;j<=i;j++) 
            {
                dp[0][i][j]+=(j-1<0?0:dp[0][i-1][j-1])+dp[0][i-1][j+1];
                dp[0][i][j]%=mod;
            }
        }
        for(int i=1;i<=m;i++)
        {
            for(int j=1;j<=min(i,n);j++)
            {
                for(int k=0;k<=i;k++)
                {
                    int d=-1+2*(s[j-1]=='(');
                    if(k-d>=0&&k-d<=m) dp[j][i][k]+=dp[j-1][i-1][k-d];
                    if(k+d<=m&&k+d>=0) dp[j][i][k]+=dp[j][i-1][k+d];
                    dp[j][i][k]%=mod;
                }
            }
        }
        cout<<dp[n][m][0]<<endl;
    }
    int main()
    {
        int t;cin>>t;
        while(t--)
        {
            solve();
        }
    }

### 数位动态规划(Digit DP)与预处理统计满足条件的数 要统计满足 $S(x^2) = S(x)^2$ 的整数 $x$ 在区间 $[L, R]$ 内的数量,可以采用数位动态规划(Digit DP)结合预处理的方法。这种方法的核心思想是将问题拆解为两个部分:计算从 0 到 $R$ 的满足条件数的数量,减去从 0 到 $L-1$ 的满足条件数的数量,从而得到区间 $[L, R]$ 内的结果 [^1]。 --- ### 数位 DP 的基本结构 数位 DP 通常以递归形式实现,使用记忆化搜索技术避免重复计算。每个状态通常包括以下参数: - `pos`:当前处理到的数位位置。 - `sum_x`:当前数字的数位和。 - `sum_x2`:当前数字平方后的数位和。 - `limit`:是否受到当前位数字的限制(即是否为原数字的前缀)。 由于需要判断 $S(x^2) = S(x)^2$,可以将 `sum_x` 和 `sum_x2` 作为状态的一部分进行递归处理 [^2]。 --- ### 状态转移与剪枝优化 在递归过程中,尝试每一位可能的数字,并计算对应的平方数位和。为了避免重复计算,可以使用记忆化缓存(如 `dp[pos][sum_x][sum_x2]`)记录已处理的状态。 此外,可以利用以下数学性质进行剪枝: - 若当前 `sum_x2 > sum_x * sum_x`,则可以直接剪枝,因为后续无法再满足等式。 - 若当前 `sum_x2 < sum_x * sum_x`,可以继续递归,直到满足条件或超出范围。 --- ### 代码实现示例 ```cpp #include <bits/stdc++.h> using namespace std; int dp[20][100][100]; // dp[pos][sum_x][sum_x2] vector<int> digits; int dfs(int pos, int sum_x, int sum_x2, bool limit) { if (pos == digits.size()) { return (sum_x2 == sum_x * sum_x); } if (!limit && dp[pos][sum_x][sum_x2] != -1) { return dp[pos][sum_x][sum_x2]; } int up = limit ? digits[pos] : 9; int res = 0; for (int i = 0; i <= up; ++i) { int new_sum_x = sum_x + i; int new_sum_x2 = sum_x2 + (i * i); // 平方数位和(暂不考虑进位) res += dfs(pos + 1, new_sum_x, new_sum_x2, limit && (i == up)); } if (!limit) { dp[pos][sum_x][sum_x2] = res; } return res; } int solve(int x) { digits.clear(); while (x > 0) { digits.push_back(x % 10); x /= 10; } reverse(digits.begin(), digits.end()); memset(dp, -1, sizeof(dp)); return dfs(0, 0, 0, true); } int main() { int L, R; cin >> L >> R; int ans = solve(R) - solve(L - 1); cout << ans << endl; return 0; } ``` 该代码使用递归函数 `dfs` 处理每一位数字,并通过 `solve` 函数将问题拆解为两个部分进行计算 [^3]。 --- ### 数学性质与进位问题 在上述代码中,`new_sum_x2` 仅计算了每一位数字的平方和,未考虑进位的影响。为了更精确地处理进位,可以在递归过程中模拟数字的平方运算,并逐位计算平方后的数位和 [^4]。 一种优化方法是预先生成所有可能的数字组合(如只包含 0~3 的数字),因为这些数字在平方时不容易产生进位,从而更容易满足 $S(x^2) = S(x)^2$ 的条件。 --- ### 预处理与优化策略 为了进一步提升性能,可以预处理所有满足条件的数字,并在查询时直接统计区间内的数量。预处理方法包括: - 生成所有只包含 0~3 的数字组合。 - 对每个组合计算其平方,并判断是否满足 $S(x^2) = S(x)^2$。 - 将满足条件的数字存入集合中,用于快速查询。 --- ### 总结 通过数位动态规划结合预处理,可以高效地统计满足 $S(x^2) = S(x)^2$ 的数字数量。该方法适用于大范围的输入,并通过剪枝和数学优化减少不必要的计算。实际应用中,还可以结合进位模拟和数字组合生成策略,进一步提升效率 [^4]。 ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值