DP小练(cf,atcoder)

Posted 佐鼬Jun

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了DP小练(cf,atcoder)相关的知识,希望对你有一定的参考价值。

F - Reordering

链接: link.

题意:

给定长度为 N N N的只包含小写字母的字符串 S S S,现在问有多少个字符串是由 S S S的子序列内的字符串排列形成的

思路:

d p ( i , j ) dp(i,j) dp(i,j)定义为用了前 i i i个字母(字母表中的字母,最多为 26 26 26个),组成长度为 j j j的字符串的数量
d p ( i , j ) = ∑ k = 0 m i n ( j , c n t [ i ] ) d p ( i − 1 , j − k ) × C j k dp(i,j)=\\sum_k=0^min(j,cnt[i]) dp(i-1,j-k)×C_j^k\\qquad dp(i,j)=k=0min(j,cnt[i])dp(i1,jk)×Cjk
现在要往前 i − 1 i-1 i1个字母长度为 j − k j-k jk的字符串中插入 k k k个第 i i i个字母了,由于之前的序列顺序通过排列组合已经有顺序了,所以只需要在长度 j j j的情况下,确定 k k k个插入的字母的相对位置,所以就是 C j k C_j^k Cjk

#include <bits/stdc++.h>
using namespace std;
const int N = 1e4 + 10;
#define int long long
const int mod = 998244353;
int dp[30][N];
string s;
int cnt[30];
int fact[N], infact[N];

int qmi(int a, int k) 
    int res = 1;
    while (k) 
        if (k & 1) res = res * a % mod;
        k >>= 1;
        a = a * a % mod;
    
    return res;

void init() 
    fact[0] = infact[0] = 1;
    for (int i = 1; i < N; i++) 
        fact[i] = fact[i - 1] * i % mod;
        infact[i] = infact[i - 1] * qmi(i, mod - 2) % mod;
    

int C(int n, int m) 
    if (m > n) return 0;
    return fact[n] * infact[m] % mod * infact[n - m] % mod;

signed main() 
    init();

    cin >> s;
    int len = s.length();

    for (int i = 0; i < len; i++) 
        cnt[s[i] - 'a' + 1]++;
    
    dp[0][0] = 1;
    for (int i = 1; i <= 26; i++) 
        for (int j = 0; j <= len; j++) 
            for (int k = 0; k <= min(j, cnt[i]); k++) 
                dp[i][j] += dp[i - 1][j - k] * C(j, k) % mod;
                dp[i][j] %= mod;
            
        
    

    int res = 0;
    for (int i = 1; i <= len; i++) 
        res += dp[26][i];
        res %= mod;
    
    cout << res << endl;

F - Variety of Digits

链接: link.

题意:

给定数字 M M M个数字
现在问 1 − N 1-N 1N中所有包含这 M M M个数字且没有前导 0 0 0的数字的和

思路:

定义 d p ( 0 , 0 / 1 , s t a t e ) dp(0,0/1,state) dp(0,0/1,state)为选择的数字在二进制下位 s t a t e state state情况下,现在选出来的数字是从高位到枚举的位置,是否小于对应位置的数字 N N N的个数
定义 d p ( 1 , 0 / 1 , s t a t e ) dp(1,0/1,state) dp(1,0/1,state)为选择的数字在二进制下位 s t a t e state state情况下,现在选出来的数字是从高位到枚举的位置,是否小于对应位置的数字 N N N的和
x x x为枚举的数字,循环是从高位往低位进行
n d p ndp ndp数组起到滚动数组的作用
这里选的数,是指 0 − 9 0-9 09这些数
那么此时 n d p ( 0 , f l a g ∣ ∣ x < a [ i ] , s t a t e ∣ 1 < < x ) + = d p ( 0 , f l a g , s t a t e ) ndp(0,flag||x<a[i],state|1<<x)+=dp(0,flag,state) ndp(0,flagx<a[i],state1<<x)+=dp(0,flag,state)
n d p ( 1 , f l a g ∣ ∣ x < a [ i ] , s t a t e ∣ 1 < < x ) + = d p ( 1 , f l a g , s t a t e ) ∗ 10 + d p ( 0 , f l a g , s t a t e ) ∗ x ndp(1,flag||x<a[i],state|1<<x)+=dp(1,flag,state)*10+dp(0,flag,state)*x ndp(1,flagx<a[i],state1<<x)+=dp(1,flag,state)10+dp(0,flag,state)x
如果是枚举的最高位置,枚举时,跳过 0 0 0即可
由于是滚动数组,所以更新完后,再把 n d p ndp ndp赋值给 d p dp dp即可

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int mod = 998244353;

string s;
int m;
int dp[2][2][1 << 10];

signed main() 
    cin >> s;
    int n = s.size();
    vector<int> a(n);
    for (int i = 0; i < n; i++) 
        a[i] = s[n - i - 1] - '0';
    
    cin >> m;

    int mask = 0;
    for (int i = 0; i < m; i++) 
        int x;
        cin >> x;
        mask |= 1 << x;
    

    for (int i = n - 1; i >= 0; i--) 
        int ndp[2][2][1 << 10];
        memset(ndp, 0, sizeof(ndp));
        for (int x = 1; x <= (i == n - 1 ? a[i] : 9); x++) 
            (ndp[0][i < n - 1 || x < a[i]][1 << x] += 1ll) %= mod;

            (ndp[1][i < n - 1 || x < a[i]][1 << x] += x) %= mod;
        
        for (int l = 0; l < 2; l++) 
            for (int s = 0; s < (1 << 10); s++) 
                for (int x = 0; x <= (l ? 9 : a[i]); x++) 
                    (ndp[0][l || x < a[i]][s | 1 << x] += dp[0][l][s]) %= mod;
                    (ndp[1][l || x < a[i]][s | 1 << x] += dp[1][l][s] * 10 + dp[0][l][s] * x) %= mod;
                
            
        
        memcpy(dp, ndp, sizeof(dp));
    
    int res = 0;
    for (int i = 0; i < 2; i++) 
        for (int j = 0; j < (1 << 10); j++) 
            if ((j & mask) == mask) 
                res += dp[1][i][j];
                res %= mod;
            
        
    
    cout << res << endl;

数位 d p dp dp+记忆化的思路,基本差不多
d f s ( p o s , s t a t e , l e a d , l i m i t ) dfs(pos,state,lead,limit) dfs(pos,state,以上是关于DP小练(cf,atcoder)的主要内容,如果未能解决你的问题,请参考以下文章

DP小练(cf,atcoder)

CF981D Bookshelves

日记12.28/题解AtCoder AGC041

CF1435 游记

AtCoder abc256全题解(区间合并模板矩阵快速幂优化dp线段树……)

AtCoder abc256全题解(区间合并模板矩阵快速幂优化dp线段树……)