20230420 训练记录:dp

Digit Sum#

1k 中有多少数是 d 的倍数。

k1010000;d100

数位 dp,dpi,s,f 表示 sxi1xi2x2x1x0(modd)f=[xi1xi2x2x1x0>ki1ki2k2k1k0]。则有(枚举新的位为 d):

dpi,s,fdpi+1,(s+d)modd,f

f=[d>k[i](d=k[i]f=1)]

展开代码
#include <bits/stdc++.h>

using ll = long long;

int main() {
    std::cin.tie(nullptr)->sync_with_stdio(false);

    std::string s;
    int d;
    std::cin >> s >> d;

    int n = s.size();
    std::vector k(n, 0);
    for (int i = 0; i < n; i++) {
        k[i] = s[n - i - 1] - '0';
    }

    const int mod = 1000000007;

    std::vector f(n + 1, std::vector(d, std::array<int, 2>{}));
    f[0][0][0] = 1;

    for (int i = 0; i < n; i++) {
        for (int s = 0; s < d; s++) {
            for (int _f : {0, 1}) {
                for (int _d = 0; _d < 10; _d++) {
                    int new_s = (s + _d) % d;
                    int new_f = _d > k[i] || (_d == k[i] && _f == 1);
                    f[i + 1][new_s][new_f] = ((ll) f[i + 1][new_s][new_f] + f[i][s][_f]) % mod;
                }
            }
        }
    }

    std::cout << (f[n][0][0] - 1 + mod) % mod << '\n';
    return 0;
}

Permutation#

Grouping#

将物品分组,如果 i,j 在一组将会得到 ai,j 的分。问所有分组情况下最大得分。

n16;|ai,j|109

分成若干组包含一个子问题:分成两组。

状压,fi 表示其中一组的分组情况为 i 的情况下的最大得分。则按照上面的划分,只需考虑其被分为 fj,fij,(ij) 的方案。即:

fi=max{fj+fij}

💡 如何枚举非空子集
for (int j = i; j; --j &= i) { }

当然,本题需要非空、真子集,因此初始 int j = i & (i - 1) (抹去最后一个 0)。

复杂度为 O(n22n+3n)

依二项式定理,k=0n(nk)2k=k=0n(nk)(1)nk(2)k=(1+2)n

sh %%%
展开代码
#include <bits/stdc++.h>

using ll = long long;

int main() {
    std::cin.tie(nullptr)->sync_with_stdio(false);

    int n;
    std::cin >> n;

    std::vector g(n, std::vector(n, 0));
    for (auto &i : g) for (auto &j : i) std::cin >> j;

    std::vector f(1 << n, -1LL);
    f[0] = 0;
    std::cout << [&, dp{[&](auto &&self, int i) -> ll {
        if (~f[i]) return f[i];
        f[i] = 0;
        for (int j = 0; j < n; j++) if (i >> j & 1) {
            for (int k = j + 1; k < n; k++) if (i >> k & 1) {
                f[i] += g[j][k];
            }
        }
        for (int j = i & (i - 1); j; --j &= i) {
            f[i] = std::max(f[i], self(self, j) + self(self, i ^ j));
        }
        return f[i];
    }}]{
        return dp(dp, (1 << n) - 1);
    }();

    return 0;
}

Subtree#

将树的节点染成黑白,问 i(1in) 在树上唯一黑色连通块的方案数。

n105,不保证模数为素数。

容易处理出以 u 为根的答案 fu=vu(fvblack+1white),考虑换根求其他点答案:

实际上 fuu 以下的答案,而另一部分当然是 u 以上的答案,即其兄弟的答案 fpfu+1,其中 up。然而不保证模数的素性就不一定有逆元,因此考虑维护 u 的兄弟节点 s{fs+1} 前缀乘积、后缀乘积。将上下两部分乘起来即可。

展开代码

可惜没有 n=m=1 的数据。

#include <bits/stdc++.h>

using ll = long long;

int main() {
    std::cin.tie(nullptr)->sync_with_stdio(false);
    
    int n, m;
    std::cin >> n >> m;
    
    if (n == 1) {
        std::cout << 1 % m << '\n';
        std::exit(0);
    }
    
    std::vector g(n, std::vector(0, 0));
    for (int i = 1; i < n; i++) {
        int u, v;
        std::cin >> u >> v;
        u -= 1, v -= 1;
        g[u].push_back(v);
        g[v].push_back(u);
    }
    
    std::vector f(n, 1);
    std::vector prefix{g}, suffix{g};
    [&, dfs{[&](auto &&self, int u, int p) -> void {
        for (int v : g[u]) if (v != p) {
            self(self, v, u);
            f[u] = (ll) f[u] * (1 + f[v]) % m;
        }

        std::fill(prefix[u].begin(), prefix[u].end(), 1);
        for (int i = 1; i < (int) g[u].size(); i++) {
            prefix[u][i] = prefix[u][i - 1];
            if (int v = g[u][i - 1]; v != p) {
                prefix[u][i] = (ll) prefix[u][i] * (f[v] + 1) % m;
            }
        }

        std::fill(suffix[u].begin(), suffix[u].end(), 1);
        for (int i = (int) g[u].size() - 2; ~i; i--) {
            suffix[u][i] = suffix[u][i + 1];
            if (int v = g[u][i + 1]; v != p) {
                suffix[u][i] = (ll) suffix[u][i] * (f[v] + 1) % m;
            }
        }
    }}]{
        dfs(dfs, 0, -1);
    }();
    
    std::vector h(n, 1);
    [&, dfs{[&](auto &&self, int u, int p, int pid) -> void {
        if (~p) {
            h[u] = ((ll) h[u] + (ll) h[p] * prefix[p][pid] % m * suffix[p][pid] % m) % m;
        }
        for (int i = 0; i < (int) g[u].size(); i++) if (int v = g[u][i]; v != p) {
            self(self, v, u, i);
        }
    }}]{
        dfs(dfs, 0, -1, -1);
    }();
    
    for (int i = 0; i < n; i++) {
        std::cout << (ll) f[i] * h[i] % m << '\n';
    }
    
    return 0;
}

Intervals#

Tower#

Grid 2#

Grid 1,但给出所有障碍点的坐标。

障碍点数 n103;h,w105

很容易计算出没有任何障碍点的答案,即从 (1,1)(n,m) 的答案实际上是二项式系数 (n1+m1n1),实现上 0-indexed 更容易。

只需要将障碍部分都减去。不用担心减重了,由容斥原理可知许多项实际上被减去、抵消、减去、抵消…… 即用 fi 表示考虑走过前 i 个障碍的方案数,为了方便可以再加一个障碍 (h1,w1),这样 fn+1 即为所求。对于每个 dp 值,都减去其前面的障碍点贡献:

fi=(xi+yixi)j<i[xjxiyjyi]×fj×((xixj)+(yiyj)xixj)

展开代码
#include <bits/stdc++.h>

using ll = long long;

int main() {
    std::cin.tie(nullptr)->sync_with_stdio(false);
    
    int h, w, n;
    std::cin >> h >> w >> n;
    
    std::vector p(n, std::pair{0, 0});

    for (auto &[x, y] : p) {
        std::cin >> x >> y;
        x -= 1, y -= 1;
    }
    
    p.emplace_back(h - 1, w - 1);
    
    const int N = 200001;
    const int mod = 1000000007;
    std::vector<int> fac(N, 0), inv(N, 0), ifc(N, 0);
    fac[0] = fac[1] = inv[0] = inv[1] = ifc[0] = ifc[1] = 1;
    
    for (int i = 2; i < N; i++) {
        fac[i] = (ll) fac[i - 1] * i % mod;
        inv[i] = ((ll) mod - mod / i) % mod * inv[mod % i] % mod;
        ifc[i] = (ll) ifc[i - 1] * inv[i] % mod;
    }
    
    auto binom = [&](int n, int m) -> ll {
        if (n < m || m < 0) return 0ll;
        return (ll) fac[n] * ifc[m] % mod * ifc[n - m] % mod;
    };
    
    std::vector f(n + 1, 0);
    std::sort(p.begin(), p.end());
    
    for (int i = 0; i < n + 1; i++) {
        auto [x, y] = p[i];
        f[i] = binom(x + y, x);
        for (int j = 0; j < i; j++) {
            auto [nx, ny] = p[j];
            if (x >= nx && y >= ny) {
                f[i] = ((ll) f[i] - (ll) f[j] * binom(x - nx + y - ny, x - nx) % mod + mod) % mod;
            }
        }
    }
    
    std::cout << f[n] << '\n';
    return 0;
}

Frog 3#


  1. https://math.stackexchange.com/questions/525266/prove-sum-binomnk2k-3n-using-the-binomial-theorem ↩︎

作者:patricky

出处:https://www.cnblogs.com/patricky/p/train-20230420.html

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

posted @   PatrickyTau  阅读(23)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
点击右上角即可分享
微信分享提示
more_horiz
keyboard_arrow_up light_mode palette
选择主题
menu