DP IV

BB

太久没更新了冒个泡! QAQ
抽了最近感觉有些意思的dp题来写
下次更新应该又是猴年马月 QAQ

Natasha, Sasha and the Prefix Sums - CodeForces 1204E

Description
求所有由n个1和m个-1组成的序列的最大前缀和之和,结果对998244853取模
0 <= n, m <= 2000
Sample Input

0 2
2 0
2 2
2000 2000

Sample Output

0
2
5
674532367

Solution - 类似卡特兰数 + DP
2400不会系列QAQ
定义DP状态dp(i, j)为有i个1和j个-1的答案,那么
dp(i,j) = dp(i - 1, j) + C(i + j - 1, i - 1) + dp(i, j - 1) - (C(i + j - 1, i) - f(i, j - 1))
其中f(i, j)为有i个1和j个-1时最大前缀和为0的方案数
如何理解?
对于已有的方案,
① 可以在 前面 放1,此时最大前缀和一定变大1,这些方案有C(i + j - 1, i - 1)个,这部分贡献为dp(i - 1, j) + C(i + j - 1, i - 1)
② 可以在 前面 放-1,此时最大前缀和若大于0,则一定减小1,这部分方案有C(i + j - 1, i) - f(i, j - 1)个,若为0则保持不变
接下来要处理的问题是如何计算f(i, j),有
f(i, j) = f(i - 1, j) + f(i, j - 1), i <= j
对于已有方案,可以选择在最后面放1或-1,都不会使得最大前缀和超过0,因为前面的最大前缀和受DP状态约束,而最后一次的前缀和为i - j <= 0
Tips:卡特兰数的推论中提到了 f(i, j) = C(i + j, j) - C(i + j, j + 1)

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<ll, int> pli;
const int N = (int)2000 + 15;
const int inf = 0x3f3f3f3f;
const int MOD = 998244853;

int f[N][N], dp[N][N], mul[N << 2], invMul[N << 2];

inline int addMod(int a, int b) {
  return a + b >= MOD ? a + b - MOD : a + b;
}

inline int quickPow(int a, int b) {
  int ans = 1, base = a;
  while(b) {
    if(b & 1) {
      ans = (ll)ans * base % MOD;
    }
    base = (ll)base * base % MOD;
    b >>= 1;
  }
  return ans;
}

inline int comb(int n, int m) {
  return n >= m ? (ll)mul[n] * invMul[m] % MOD * invMul[n - m] % MOD : 0;
}

inline void init() {
  mul[0] = invMul[0] = 1;
  for(int i = 1; i < (N << 2); i++) {
    mul[i] = (ll)mul[i - 1] * i % MOD;
    invMul[i] = quickPow(mul[i], MOD - 2);
  }
}

int main() {
  init();

  for(int y = 1; y < N; y++) {
    f[0][y] = 1;
  }
  for(int x = 1; x < N; x++) {
    for(int y = x; y < N; y++) {
      f[x][y] = addMod(f[x - 1][y], f[x][y - 1]);
    }
  }

  for(int x = 1; x < N; x++) {
    dp[x][0] = x;
    for(int y = 1; y < N; y++) {
      dp[x][y] = addMod(addMod(dp[x - 1][y], comb(x + y - 1, x - 1)), addMod(dp[x][y - 1], MOD - addMod(comb(x + y - 1, x), MOD - f[x][y - 1])));
    }
  }

  int n, m;
  while(~scanf("%d%d", &n, &m)) {
    printf("%d\n", dp[n][m]);
  }
}


Mysterious Code - Codeforces 1163D

Description
定义f(s, t)t串在s串中的出现次数
给定串c, s, t,其中c串中可能出现*字符,每一个*都必须替换为任意一个字符(小写字母),问f(c, s) - f(c, t)
s, t只包含小写字母,且长度不超过50,c串长度不超过1000
Sample Input

*****
katie
shiro

caat
caat
a

*a*
bba
b

***
cc
z

Sample Output

1
-1
0
2

Solution - KMP + DP
因为问的是次数,所以可以考虑用KMP预处理串s, t,得到next数组 定义dp状态为dp(i, j, k),代表当串c匹配到第i位时,串s在next状态j下,串t在next状态k下的答案,那么通过枚举i, j, k,向前转移状态,最终答案为 max{dp(n, j, k)},其中n为串c的长度

#include <bits/stdc++.h>
#define lson l, m, rt << 1
#define rson m + 1, r, rt << 1 | 1
using namespace std;
typedef long long ll;
const int N = (int)1000 + 15;
const int MOD[2] = {(int)1e9 + 7, 1234567891};
const int SIZE = 26;
const int inf = 0x3f3f3f3f;

struct KMP {
    char s[N];
    int nxt[N];

    void getNext() {
        nxt[1] = 0;
        int i = 1, j = 0;
        while(s[i]) {
            if(j == 0 || s[i] == s[j]) {
                i++;
                j++;
                nxt[i] = j;
            } else {
                j = nxt[j];
            }
        }
    }

    int move(int len, char ch) {
        int pos = len;
        if(s[pos + 1] == 0) {
            pos = nxt[pos];
        }
        for(; pos && s[pos] != ch; pos = nxt[pos]);
        return pos;
    }
};

KMP ss, st;
char sc[N];
int dp[N][52][52];

int main() {
    while(~scanf("%s%s%s", sc + 1, ss.s + 1, st.s + 1)) {
        memset(dp, 0x80, sizeof(dp));
        dp[0][0][0] = 0;
        ss.getNext();
        st.getNext();
        int p = strlen(sc + 1), n = strlen(ss.s + 1), m = strlen(st.s + 1);
        for(int i = 0; i < p; i++) {
            for(int j = 0; j < n; j++) {
                for(int k = 0; k < m; k++) {
                    if(dp[i][j][k] == (int)0x80808080) {
                        continue;
                    }
                    if(sc[i + 1] != '*') {
                        int nj = ss.move(j + 1, sc[i + 1]);
                        int nk = st.move(k + 1, sc[i + 1]);
                        dp[i + 1][nj][nk] = max(dp[i + 1][nj][nk], dp[i][j][k] + (j + 1 == n && ss.s[j + 1] == sc[i + 1]) - (k + 1 == m && st.s[k + 1] == sc[i + 1]));

                    } else {
                        for(char ch = 'a'; ch <= 'z'; ch++) {
                            int nj = ss.move(j + 1, ch);
                            int nk = st.move(k + 1, ch);
                            dp[i + 1][nj][nk] = max(dp[i + 1][nj][nk], dp[i][j][k] + (j + 1 == n && ss.s[j + 1] == ch) - (k + 1 == m && st.s[k + 1] == ch));
                        }
                    }
                }
            }
        }

        int ans = -inf;
        for(int j = 0; j < n; j++) {
            for(int k = 0; k < m; k++) {
                ans = max(ans, dp[p][j][k]);
            }
        }
        printf("%d\n", ans);
    }
}


Steps to One - Codeforces 1139D

Description
给定一个空数组和一个数m (1 < m <= 100000)
每次随机从[1, m]中随机选择一个整数,将其加入数组中,直到数组内的数的gcd为1
问数组的期望长度
Sample Input

1
2
4

Sample Output

1
2
333333338

Solution - DP + 莫比乌斯反演
这题可能别的题解更简单,这里谈谈本菜鸡的做法QAQ (伪 · DP) 设dp(i, k)k | gcd时的概率,则显然dp(i, k) = dp(i - 1, k) * ceil(m / k) / mdp(0, k) = 1,那就没有dp的必要性了,直接写成dp(i, k) = (ceil(m / k) / m) ^ i,当k >= 2时,期望长度为sum((i + 1) * dp(i, k)),这里i + 1是因为要在末尾加一个数使得gcd = 1,而这个数带来的概率与期望暂时不计算,在最后再计算
f(k) = sum((i + 1) * dp(i, k)) = sum((i + 1) * (ceil(m / k) / m) ^ i),设a = 1 / (m / ceil(m / k)),故f(k) = sum((i + 1) / a^i),对齐展开可得下两式
f(k) = 2 / a^1 + 3 / a^2 + 4 / a^3 + ...
af(k) = 2 / a^0 + 3 / a^1 + 4 / a^2 + ...
裂项相消,得
(a - 1)f(k) = 2 / a^0 + 1 / a^1 + 1 / a^2 + 1 / a^3 + ...
(a - 1)f(k) = 1 + a / (a - 1)
f(k) = (2a - 1) / (a - 1)^2
h(k)gcd = k时的期望,则h(1) = 1 / mf(k) = h(k) + h(2k) + ...,用莫比乌斯反演求出h(x) = sum{ k = 1 -> m / x, mu(k) * f[x * k] },这样便可求出h(k)
最后计算期望,需要把最后那个导致gcd = 1的数也算入其中
g(k) = sum { x = 1 -> m, [gcd(x, m) = 1] },再来一波反演(省略过程了),可得g(k) = sum( d: d | k, mu(d) * ceil(m / d) ),因此g(k)可以在nlogn时间内预处理掉
最后Answer = h(1) + sum(h(k) * g(k) / m)

// scanf printf push_back emplace_back memset sizeof
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
const int N = (int)1e5 + 15;
const ll inf = (ll)1e12;
const int MOD = (int)1e9 + 7;

int mu[N];
bool isprime[N];
int prime[N], tot;
int g[N], inv[N], f[N];

inline int addMod(int a, int b) {
    return a + b >= MOD ? a + b - MOD : a + b;
}

inline int quickPow(int a, ll b) {
  int ans = 1, base = a;
  while(b) {
    if(b & 1) {
      ans = 1LL * ans * base % MOD;
    }
    base = 1LL * base * base % MOD;
    b >>= 1;
  }
  return ans;
}

void init(){
    inv[1] = 1;
    for(int i = 2; i < N; i++) {
        inv[i] = (ll)(MOD - MOD / i) * inv[MOD % i] % MOD;
    }

    memset(isprime, true, sizeof(isprime));
    tot = 0, mu[1] = 1;
    for(int i = 2; i < N; i++){
        if(isprime[i]){
            prime[tot++] = i;
            mu[i] = MOD - 1;
        }
        for(int j = 0; j < tot && i * prime[j] < N; j++){
            isprime[i * prime[j]] = false;
            if(i % prime[j] == 0){
                mu[i * prime[j]] = 0;
                break;
            }else{
                mu[i * prime[j]] = MOD - mu[i];
            }
        }
    }
}


int main() {
    init();
    int m;
    while(~scanf("%d", &m)) {
        memset(g, 0, sizeof(g));
        for(int d = 1; d <= m; d++) {
            for(int k = d; k <= m; k += d) {
                g[k] = addMod(g[k], 1LL * mu[d] * (m / d) % MOD);
            }
        }
        for(int k = 1; k <= m; k++) {
            int a = 1LL * m * inv[m / k] % MOD;
            f[k] = 1LL * addMod(addMod(a, a), MOD - 1) % MOD * quickPow(addMod(a, MOD - 1), 2LL * MOD - 4) % MOD;
        }
        int ans = inv[m];
        for(int x = 2; x <= m; x++) {
          int sum = 0;
          for(int k = 1; k <= m / x; k++) {
            sum = addMod(sum, 1LL * mu[k] * f[x * k] % MOD);
          }
          ans = addMod(ans, 1LL * sum * g[x] % MOD * inv[m] % MOD);
        }
        printf("%d\n", ans);
    }
}