Codeforces Global Round 14 E. Phoenix and Computers dp+排列组合

Codeforces Global Round 14 E. Phoenix and Computers dp+排列组合

    • 题意
    • 思路
    • Code(436MS)

传送门: https://codeforces.com/contest/1515/problem/E
赛 中 想 了 一 个 多 小 时 , 还 是 处 理 不 了 一 些 细 节 。 赛中想了一个多小时,还是处理不了一些细节。

题意

有 n 个 电 脑 排 在 一 排 , 你 可 以 手 动 打 开 任 意 电 脑 , 但 是 有 个 特 点 , 有n个电脑排在一排,你可以手动打开任意电脑,但是有个特点, n
对 于 一 个 电 脑 , 如 果 它 左 右 都 被 打 开 了 , 那 么 它 会 自 动 打 开 。 对于一个电脑,如果它左右都被打开了,那么它会自动打开。

最 后 问 , 有 多 少 种 全 部 开 机 方 案 ? 最后问,有多少种全部开机方案?

思路

我 们 考 虑 两 种 方 式 , 第 一 种 全 部 都 是 手 动 打 开 , 第 二 种 带 有 自 动 打 开 。 我们考虑两种方式,第一种全部都是手动打开,第二种带有自动打开。

  • 全 部 手 动 打 开 全部手动打开

对 于 电 脑 1 , 如 果 要 全 部 手 动 开 启 , 则 需 要 按 照 1 , 2... n 的 顺 序 打 开 , C n − 1 0 。 对于电脑1,如果要全部手动开启,则需要按照1,2...n的顺序打开,C_{n-1}^0。 112...nCn10
对 于 电 脑 2 , 如 果 要 全 部 手 动 开 启 , 则 后 面 必 须 按 照 3 , 4.. n 的 顺 序 , 前 面 必 须 1 , C n − 1 1 对于电脑2,如果要全部手动开启,则后面必须按照3,4..n的顺序,前面必须1,C_{n-1}^1 23,4..n1Cn11
对 于 电 脑 k , 如 果 要 全 部 手 动 开 启 , 则 后 面 必 须 要 按 照 k + 1 , k + 2... n 的 顺 序 , 前 面 必 须 是 k − 1 , . . . 1 的 顺 序 , 所 有 要 在 n − 1 中 选 k − 1 个 开 前 面 , C n − 1 k − 1 对于电脑k,如果要全部手动开启,则后面必须要按照k+1,k+2...n的顺序,前面必须是k-1,...1的顺序,所有要在n-1中选k-1个开前面,C_{n-1}^{k-1} kk+1,k+2...nk1,...1n1k1Cn1k1

则 总 方 案 数 为 C n − 1 0 + C n − 1 1 + . . . + C n − 1 n − 1 = 2 n − 1 则总方案数为C_{n-1}^0+C_{n-1}^1+...+C_{n-1}^{n-1}=2^{n-1} Cn10+Cn11+...+Cn1n1=2n1

也 就 是 说 , 对 于 x 台 电 脑 , 如 果 全 部 手 动 开 启 的 话 , 有 2 x − 1 种 方 案 数 , 下 面 会 用 到 。 也就是说,对于x台电脑,如果全部手动开启的话,有2^{x-1}种方案数,下面会用到。 x2x1

  • 如 果 有 自 动 打 开 如果有自动打开

自 动 打 开 只 会 出 现 现 在 左 右 都 打 开 后 才 会 自 动 打 开 , 所 以 假 设 这 么 一 种 开 机 方 式 : 自动打开只会出现现在左右都打开后才会自动打开,所以假设这么一种开机方式:
设 有 x k 台 电 脑 自 动 开 启 。 设有x_k台电脑自动开启。 xk
1 到 x 1 − 1 手 动 打 开 , x 1 + 1 到 x 2 − 1 手 动 打 开 , . . . x k + 1 到 n 手 动 打 开 。 ( x k + 1 ≤ N ) 1到x_1-1手动打开,x1+1到x_2-1手动打开,...x_k+1到n手动打开。(x_k+1\leq N) 1x11x1+1x21...xk+1n(xk+1N)
这 样 , x k 台 都 满 足 自 动 打 开 的 要 求 。 这样,x_k台都满足自动打开的要求。 xk

然 后 就 是 要 怎 么 进 行 这 个 过 程 。 然后就是要怎么进行这个过程。
设 f [ i ] [ j ] 为 前 i 台 电 脑 中 , 有 j 台 是 手 动 打 开 , 第 i 台 也 是 手 动 打 开 , 并 且 第 i + 1 台 自 动 开 启 的 方 案 数 。 设f[i][j]为前i台电脑中,有j台是手动打开,第i台也是手动打开,并且第i+1台自动开启的方案数。 f[i][j]ijii+1

则 我 们 想 让 第 i + 1 台 成 为 自 动 打 开 的 电 脑 , 则 i + 1 后 面 几 台 电 脑 手 动 打 开 , 设 为 k 台 。 则我们想让第i+1台成为自动打开的电脑,则i+1后面几台电脑手动打开,设为k台。 i+1i+1k

则 就 是 f [ i ] [ j ] 向 f [ i + 1 + k ] [ j + k ] 的 转 移 , 怎 么 转 移 呢 ? 则就是f[i][j]向f[i+1+k][j+k]的转移,怎么转移呢? f[i][j]f[i+1+k][j+k]

f [ i + 1 + k ] [ j + k ] 比 f [ i ] [ j ] 多 了 k 台 手 动 开 启 。 f[i+1+k][j+k]比f[i][j]多了k台手动开启。 f[i+1+k][j+k]f[i][j]k
这 k 台 我 们 称 为 “ 新 ” 打 开 的 电 脑 , j 台 我 们 称 为 “ 旧 ” 打 开 的 电 脑 。 这k台我们称为“新”打开的电脑,j台我们称为“旧”打开的电脑。 kj
第 i + 1 是 自 动 打 开 的 。 第i+1是自动打开的。 i+1
之 前 我 们 处 理 过 , k 台 手 动 开 启 的 方 案 数 为 2 k − 1 , 然 后 我 们 要 将 k 台 和 j 台 一 起 合 并 。 之前我们处理过,k台手动开启的方案数为2^{k-1},然后我们要将k台和j台一起合并。 k2k1kj
那 么 就 是 有 j + k 台 中 , 有 k 台 是 “ 新 ” 电 脑 , 方 案 数 为 C k + j k 。 那么就是有j+k台中,有k台是“新”电脑,方案数为C_{k+j}^k。 j+kkCk+jk

则 转 移 方 程 为 f [ i + 1 + k ] [ j + k ] = f [ i ] [ j ] ∗ 2 k − 1 ∗ C j + k k 。 则转移方程为f[i+1+k][j+k]=f[i][j]*2^{k-1}*C_{j+k}^k。 f[i+1+k][j+k]=f[i][j]2k1Cj+kk

i 、 j 、 k 三 层 循 环 进 行 转 移 , O ( n 3 ) 是 可 以 的 。 i、j、k三层循环进行转移,O(n^3)是可以的。 ijkO(n3)

最 后 预 处 理 组 合 数 进 行 转 移 即 可 。 最后预处理组合数进行转移即可。

Code(436MS)

#include "bits/stdc++.h"
using namespace std;

typedef long long ll;

ll n, mod;
const int N = 410;
ll F[N], invn[N], invF[N], bit[N];

void init() {
     
    bit[1] = F[0] = F[1] = invn[0] = invn[1] = invF[0] = invF[1] = 1;
    for(int i = 2;i < N; i++){
     
        F[i] = F[i - 1] * i % mod;
        invn[i] = (mod - mod / i) * invn[mod % i] % mod;
        invF[i] = invF[i - 1] * invn[i] % mod;
        bit[i] = bit[i - 1] * 2 % mod;
    }
}

ll C(ll m, ll n) {
     
    if(m < 0 || n < 0 || n > m)
        return 0;
    ll ans = F[m];
    ans = ans * invF[n] % mod;
    ans = ans * invF[m - n] % mod;
    return ans;
}

void solve() {
     
    cin >> n >> mod;
    init();
    vector<vector<ll>> dp(n + 1, vector<ll>(n + 1, 0));

    for(int i = 1;i <= n; i++) {
      // 前i台电脑
        dp[i][i] = bit[i];
        for(int j = 0;j <= i; j++) {
      // i台中有j台是手动打开的
            for(int k = 1;k + i + 1 <= n; k++) {
      // i + 1是自动打开的,然后后面k台需要手动打开
                dp[i + 1 + k][j + k] = (dp[i + 1 + k][j + k] + dp[i][j] * bit[k] % mod * C(j + k, k) % mod) % mod;
            }
        }
    }

    ll ans = 0;
    for(int i = 0;i <= n; i++) {
     
        ans = (ans + dp[n][i]) % mod;
    }

    cout << ans << endl;
}

signed main() {
     
    solve();
}

你可能感兴趣的:(动态规划,排列组合)