20230419 训练记录:dp

Posted Patricky

tags:

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

#dp

Deque

Alice 和 Bob 玩游戏,轮流从 deque 的头/尾取走一个元素,Alice 拿走的数和为 \\(X\\),Bob 拿走的数和为 \\(Y\\)。Alice 想最大化 \\(X - Y\\) 而 Bob 想最小化 \\(X - Y\\)。问双方都采取最优策略情况下的 \\(X - Y\\) 为多少。

\\(n \\leq 3000; a_i \\leq 10^9\\)

\\(f_l, r, 0 / 1\\) 表示 Bob / Alice 先手情况下,在区间 \\([l, r]\\) 的最佳答案,所求即 \\(f_0, n - 1, 1\\)。有:

\\[\\beginaligned f_l, r, 0 &= \\min(f_l + 1, r, 1 - a_l, f_l, r - 1, 1 - a_r)\\\\ f_l, r, 1 &= \\max(f_l + 1, r, 0 + a_l, f_l, r - 1, 0 + a_r)\\\\ \\endaligned \\]

展开代码
#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<int> a(n);
    for (int &i : a) std::cin >> i;

    const ll inf = 1e18;
    std::vector f(n, std::vector(n, std::array<ll, 2>inf, inf));

    std::cout << [&, dp[&](auto &&self, int l, int r, bool alice) -> ll 
        if (f[l][r][alice] != inf) return f[l][r][alice];

        ll ans = 0;
        if (l == r) ans = a[l] * (alice ? 1 : -1);
        else if (alice) ans = std::max(self(self, l + 1, r, 0) + a[l], self(self, l, r - 1, 0) + a[r]);
        else ans = std::min(self(self, l + 1, r, 1) - a[l], self(self, l, r - 1, 1) - a[r]);

        return f[l][r][alice] = ans;
    ]
        return dp(dp, 0, n - 1, 1);
    ();

    return 0;

Candies

\\(k\\) 拆成 \\(n\\) 个整数的方案数(整数划分数),但是 \\(0 \\leq x_i \\leq a_i\\)

\\(n \\leq 100; k \\leq 10^5\\)

整数划分数

整数划分数其实就是完全背包,有:

f[0] = 1;
for (int i = 1; i <= n; i++) 
	for (int j = i; j <= n; j++) 
	 	f[j] += f[j - i];
	 

带上限制,用 \\(f_i, j\\) 表示使用前 \\(i\\) 个满足题意的数组成的和为 \\(j\\) 的方案数,所求即 \\(f_n, k\\)。有:

\\[\\beginaligned f_i, j = \\sum\\limits_k = 0^\\min(a_i, j) f_i - 1, j - k = \\sum\\limits_k = \\max(0, j - a_i)^j f_i - 1, k \\endaligned \\]

右侧为一段区间和,考虑使用前缀和优化。

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

using ll = long long;

int main() 
    std::cin.tie(nullptr)->sync_with_stdio(false);
    
    int n, k;
    std::cin >> n >> k;
    
    std::vector<int> a(n);
    for (int &i : a) std::cin >> i;
        
    std::vector f(n + 1, std::vector(k + 1, 0LL)), sf;
    
    const int mod = 1000000007;
    
    f[0][0] = s[0][0] = 1;
    for (int i = 1; i <= k; i++) 
        s[0][i] = (s[0][i - 1] + f[0][i]) % mod;
    

    for (int i = 1; i <= n; i++) 
        f[i][0] = s[i][0] = 1;
        for (int j = 1; j <= k; j++) 
            if (int ai = a[i - 1]; j <= ai) 
                f[i][j] = s[i - 1][j];
             else 
                f[i][j] = ((s[i - 1][j] - s[i - 1][(j - ai) - 1]) % mod + mod) % mod;
            
            s[i][j] = (s[i][j - 1] + f[i][j]) % mod;
        
    

    std::cout << f[n][k] << \'\\n\';

    return 0;

Slimes

同合并石子,区间 dp 模板题。满足四边形不等式。

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

int read() 
    int x = 0, f = 1, c = getchar();
    for (; !isdigit(c); c = getchar()) if (c == \'-\') f = -1;
    for (; isdigit(c); x = x * 10 + c - \'0\', c = getchar());
    return x * f;


const int N = 410;
using ll = long long;

const ll inf = 1E18;
int n, a[N], s[N][N];
ll f[N][N], sum[N];

#define w(i, j) sum[j] - sum[i - 1]

int main() 
    n = read();
    for (int i = 1; i <= n; i++) 
        a[i] = read();
    

    for (int i = 1; i <= n; i++) 
        sum[i] = sum[i - 1] + a[i];
        s[i][i] = i;
    

    for (int l = 2; l <= n; l++) 
        for (int i = 1, j = l; j <= n; i++, j++) 
            f[i][j] = inf;
            for (int k = s[i][j - 1]; k <= s[i + 1][j]; k++) 
                if (f[i][j] > f[i][k] + f[k + 1][j] + w(i, j)) 
                    f[i][j] = f[i][k] + f[k + 1][j] + w(i, j);
                    s[i][j] = k;
                
            
        
    

    printf("%lld\\n", f[1][n]);

    return 0;

Matching

\\(n\\)\\(n\\) 女,不同性别之间配对,如果 \\(a_i, j = 1\\) 表示 \\(i\\)\\(j\\) 合适。只能一男一女,问多少合法方案数。

\\(n \\leq 21\\)

\\(n\\) 很小,考虑枚举子集。\\(f_i, s\\) 表示 \\([1, i]\\) 编号的男女,配对情况为 \\(s\\) 的方案数。则有:

\\[f_i + 1, s \\lor j := f_i + 1, s \\lor j + f_i, s \\]

展开代码
#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 a(n, std::vector(n, 0));
    for (auto &i : a) for (auto &j : i) std::cin >> j;
    
    std::vector f(n + 1, std::vector(1 << n, 0LL));
    f[0][0] = 1;
    
    const int mod = 1000000007;
    
    for (int s = 0; s < 1 << n; s++) 
        for (int i = 0; i < n; i++) if (f[i][s]) 
            for (int j = 0; j < n; j++) if ((~s >> j & 1) && a[i][j]) 
                (f[i + 1][s | (1 << j)] += f[i][s]) %= mod;
            
        
    
    
    std::cout << f.back().back() << \'\\n\';
    
    return 0;

Indepedent Set

将树的所有节点染成黑白两色,求相邻节点不同时为黑的方案数。

\\(n \\leq 10^5\\)

乘法原理,一路乘上去,如果当前节点是白色就两种加起来,否则为黑色就乘以儿子涂成白色的方案数。

展开代码
#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(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);
    

    const int mod = 1000000007;
    std::vector f(n, std::array<int, 2>);

    [&, dfs[&](auto &&self, int u, int p) -> void 
        f[u][0] = f[u][1] = 1;
        for (auto v : g[u]) if (v != p) 
            self(self, v, u);
            f[u][0] = (ll) f[u][0] * ((ll) f[v][0] + f[v][1]) % mod;
            f[u][1] = (ll) f[u][1] * f[v][0] % mod;
        
    ]
        dfs(dfs, 0, -1);
    ();

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

    return 0;

Flowers

给定 \\(\\h\\_n, \\a\\_n\\),求最长子序列 \\(\\p\\_k\\) 使得 \\(h_p_i\\) 上升的最大的 \\(\\sum \\limits_i = 1 ^ k a_p_i\\)

\\(h_i \\leq n \\leq 2 \\times 10^5; a_i \\leq 10^9\\)

\\(f_i\\) 表示考虑到第 \\(i\\) 个元素结尾的最长上升子序列对应的最大和,则:

\\[f_i = \\max_h_j \\lt h_i \\ f_j \\ + a_i \\]

查询前缀最大值,此事树状数组能胜任。注意到 \\(h_i\\) 很小,可以用下标表示 \\(h_i\\),值表示 dp 的值。

展开代码
#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 h(n, 0), a(n, 0);
    std::vector p(n + 1, 0LL);
    for (auto &i : h) std::cin >> i;
    for (auto &i : a) std::cin >> i;
    
    ll f = 0, ans = 0;
    for (int i = 0; i < n; i++) 
        ll res = 0;
        for (int x = h[i] - 1; x; x -= x & -x)
            res = std::max(ans, p[x]);
        f = res + a[i];
        for (int x = h[i]; x <= n; x += x & -x)
            p[x] = std::max(p[x], f);
        ans = std::max(ans, f);
    
    
    std::cout << ans << \'\\n\';
    
    return 0;

Walk

求长度为 \\(k\\) 的路径条数。

邻接矩阵 \\(n \\leq 50; k \\leq 10^18\\)

熟知 Floyd 算法的 \\(f_i, j\\) 循环 \\(k\\) 次表示的是走 \\(k\\) 步的最短路径(这也是矩阵乘法的本质之一)。又因为 \\(f_k = f_1^k\\) 所以这是一道矩乘快速幂模板题。

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

using ll = long long;

const int N = 50;
const int mod = 1000000007;

using matrix = std::array<std::array<int, N>, N>;

int n;

matrix operator *(const matrix &lhs, const matrix &rhs) 
    matrix res;
    for (int i = 0; i < n; i++) 
        for (int k = 0; k < n; k++) 
            for (int j = 0; j < n; j++) 
                res[i][j] = ((ll) res[i][j] + (ll) lhs[i][k] * rhs[k][j] % mod) % mod;
            
        
    
    return res;


matrix power(matrix x, ll k) 
    matrix res;
    for (int i = 0; i < n; i++) res[i][i] = 1;
    for (; k; k /= 2, x = x * x)
        if (k & 1) res = res * x;
    return res;


int main() 
    std::cin.tie(nullptr)->sync_with_stdio(false);
    
    ll k;
    std::cin >> n >> k;
    
    matrix x;
    for (int i = 0; i < n; i++) 
        for (int j = 0; j < n; j++) 
            std::cin >> x[i][j];
        
    

    matrix ans = power(x, k);
    
    int res;
    for (int i = 0; i < n; i++) 
        for (int j = 0; j < n; j++) 
            res = ((ll) res + ans[i][j]) % mod;
        
    

    std::cout << res << \'\\n\';
    
    return 0;

Dailight 训练记录

现场赛记录

19 CCPC 湘潭邀请赛  11/Gold

19 ICPC 西安邀请赛 49/Silver

 

训练规划:

hl:

1.深入增强图论,数据结构的能力,包括但不限于树形dp,点分治,多写dp,学习斜率优化,四边形优化

2.保证银牌及以下图论,数据结构,dp的通过率,最好可以在十分钟内出思路

3.养成提交前检查代码的习惯,不出现傻逼错误。

4.学习简单数论(逆元,组合数),尤其是数论结合图论的应用

 

gbs:

1.加快上机 ->  写完代码过样例 这一过程的速度。

2.减少debug占用机时

3.首要任务是增加银牌及以下数论的通过率,包括但不限于打表找规律,基础推公式,其次学习较难的数论。

4.增加用简要语言表述题意的能力

 

zcz.

1.训练计算几何的能力,保证简单及中等计算几何的通过率

2.多看一些思维题及找规律,推公式,构造题,写代码为次,提出正确思路为主。

3.写题的过程尽量了解ACM的出题套路。

 

总:

1.所有队员多做思维题,保证脑子灵活度

2.多开训练赛,增加讨论题目的配合能力以及队伍机时调度的能力

 

经验总结:

1.交之前务必重新审视两边代码保证正确,WA之后直接下机并且打印代码,2WA之后需要队友确认题意并且检查代码

2.签到题签完之后如果过的人数中等且写题的十分自信,可以本题单线程,否则需要其他至少一位队友确认题意并且确认写法才可敲题

3.开局开荒题的队员除非特别有自信且剩余码量不高,否则优先将机子让给队友写签到题以降低罚时

4.非最后十分钟垃圾时间严禁挂机,当前没有题挂在线程上的队员就去读没有读过的题

5.难题采用数论给gbs和zcz讨论,图论数据结构给hl,计算几何给zcz,无法确定类型的读给hl确认类型的战略,确认题是否可做

 

训练赛记录

The 10th Shandong Provincial Collegiate Programming Contest   11/13     rak3/249

以上是关于20230419 训练记录:dp的主要内容,如果未能解决你的问题,请参考以下文章

20230419碎碎念

dp暑假专题 训练记录

Dailight 训练记录

ECNA 2014 部分题解 | 训练记录0703

ECNA 2014 部分题解 | 训练记录0703

斜率优化系列——训练记录