zoukankan      html  css  js  c++  java
  • 题解 smoj 2806 【建筑物】

    题解 smoj 2806 【建筑物】

    时间:2019.3.15

    DP of DP of DP

    题目描述

    有R红色立方体,G绿色立方体和B蓝色立方体。每个立方体的边长是1。现在有一个N × N的木板,该板被划分成1×1个单元。现在要把所有的R+G+B个立方体都放在木板上。立方体必须放置在单元格内,单元格可以竖立放置多个立方体。

    放置在板上的立方体可以被视为“建筑物”。一个“建筑物”被称为“美丽建筑物”,当且仅当:人站在南面,向北面望过去,观察建筑物时,所有可见立方体都是相同的颜色。

    例如,在下图中,左侧建筑物是“美丽建筑物”,而右侧建筑物则不是。

    问题是:给出R,G,B,N,有多少种不同的“美丽建筑物”,答案模1000000007。

    分析

    数数题,考虑DP。

    下文为了方便,用\(A, B, C\)来表示\(R, G, B\)

    不妨钦定颜色\(A\)在前面

    钦定颜色\(A\)是蓝色

    \(F(N, A, B, C)\)为前\(i\)列,三种颜色分别使用\(A, B, C\)个的方案数,

    可以发现每一列都是独立的,即只要知道该列使用三种颜色的各自块数,该列的方案数就唯一确定。

    \(G(A, B, C)\)为单列使用三种颜色立方体各\(A, B, C\)个时的方案数。

    枚举这一列的高度\(h\),设\(S(i, A, B, C, h)\)为这一列前\(i\)栋“楼房”,三种颜色分别使用\(A, B, C\)个,且最高一栋高度为\(h\)的方案数。

    转移

    1. 对于\(F(N, A, B, C)\),枚举第\(N\)列所使用的\(A', B', C'\),答案加上\(F(N - 1, A - A', B - B', C - C') \times G(A', B', C')\)

    2. 对于\(G(A, B, C)\),枚举这一列的最高高度\(h\),答案加上\(S(N, A, B, C, h)\)

    3. 对于\(S(i, A, B, C, h)\),分情况讨论:

      • 高度为\(h\)的楼房在前\(i - 1\)栋中,枚举第\(i\)栋楼房的高度\(h_2\),并枚举\(A', B'\),可以算出\(C' = h_2 - A' - B'\)

        答案加上\(S(i - 1, A - A', B - B', C - C', h) \times MultiP(A', B', C')\)

      • \(i\)栋楼房高度是\(h\),枚举前\(i - 1\)栋楼房的最高高度\(h_2\),仍然枚举\(A', B'\),算出\(C' = h - A' - B'\)

        答案加上\(S(i - 1, A - A', B - B', C - C', h_2) \times MultiP(A' - (h - h_2), B', C')\)

        这里注意楼房顶层的\((h - h_2)\)块立方体颜色已经确定了。所以计算多重排列时要将这一栋的\(A'\)减去\((h - h_2)\)

    转成公式,就是:

    1:

    \[\large F(N, A, B, C) = \displaystyle \sum _ {A', B', C'} F(N - 1, A - A', B - B', C - C') \times G(A', B', C') \\ \]

    2:

    \[\large G(A, B, C) = \displaystyle \sum _ {h \le A} S(N, A, B, C, h) \\ \]

    3:

    \[\large S(i, A, B, C, h) \\ =\displaystyle \sum _ {A' + B' + C' = h_2 \le h} S(i - 1, A - A', B - B', C - C', h) \times MultiP(A', B', C') \\ +\displaystyle \sum _ {h_2 \le A' + B' + C' = h} S(i - 1, A - A', B - B', C - C', h_2) \times MultiP(A' - (h - h_2), B', C') \]

    时间复杂度:\(O(N^8)\)

    优化 1

    我们发现只有颜色\(A\)对答案才有关键影响,因此我们可以将颜色\(B, C\)合并,统一当成同一种颜色\(BC\)

    在最后计算答案时将\(F\)函数乘上\(C(BC, B)\),从而将\(B\)\(C\)\(BC\)中分离出来。

    实现上,可以直接将\(B+C\)代入\(O(N^8)\)算法的\(B\)中,将\(C\)置为\(0\)

    \[答案 = F(N, A, B +C, 0) \times C(B +C, B) \]

    优化2

    运用优化1的思想,我们发现\(F\)\(G\)中的\(A\)已必不可少。但仔细思考发现在同一列的所有蓝色立方体中,只有最靠前(面向正面)\(h\)个可以对答案产生关键影响。因此我们可以在\(G\)中运用组合数,将\(S\)中的\(A\)一维去掉。

    优化后的DP方程

    \(F(N, A, BC)\)为在前\(N\)列中,使用\(A\)个蓝色立方体和共\(BC\)个红、绿色立方体摆成合法建筑物的方案数。

    \(G(A, BC)\)为在单独一列中,使用\(A\)个蓝色立方体和共\(BC\)个红、绿色立方体摆成合法建筑物的方案数。

    \(S(i, cnt, h)\)为在单独一列的前\(i\)栋楼房中,共使用\(cnt\)个立方体,摆成楼房最高刚好为\(h\)层的方案数。

    转移方程:

    1:

    \[\large F(N, A, BC) = \displaystyle \sum _ {A', BC'} F(N - 1, A - A', BC - BC') \times G(A', BC') \]

    2:

    \[\large G(A, BC) = \displaystyle \sum _ {h \le A} S(N, A + BC, h) \times C(A + BC - h, A - h) \]

    3:

    \[\large S(i, cnt, h) =\displaystyle \sum _ {h_2 \le h} S(i - 1, cnt - h_2, h) +\displaystyle \sum _ {h_2 < h} S(i - 1, cnt - h, h_2) \]

    答案:

    \[\large 答案 = F(N, A, B + C) \times C(B + C, B) \]

    注意事项

    在方程2中,组合数是\(C(A + BC - h, A - h)\)而非\(C(A + BC, A)\)

    这是因为有刚好\(h\)个蓝色方块的是唯一确定的。

    代码

    为了调试加了很多条件编译,有点丑

    #include <bits/stdc++.h>
    // 0: Normal
    // 1: Debugging
    // 2: Debugging (test output)
    // 3: Submit
    #define __MODE__ 3
    #if __MODE__ == 1 || __MODE__ == 2
    int tab_cnt;
    #define TAB { for (int i = 1; i <= tab_cnt; i++) printf("\t"); }
    #define FUNC_IN(...) { TAB printf(__VA_ARGS__); tab_cnt++; }
    #define FUNC_OUT(...) { TAB printf(__VA_ARGS__); tab_cnt--; }
    #else
    #define TAB
    #define FUNC_IN(...)
    #define FUNC_OUT(...)
    #endif
    using namespace std;
    typedef long long LL;
    const int kN = 25 + 5;
    const int kMod = 1000000007;
    // MultiP
    const int kPriCnt = 10;
    const int kPri[kPriCnt] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29};
    int factor_cnt[kPriCnt];
    void FactorDecom(int x, int sign) {
      for (int i = 0; i < kPriCnt; i++) {
        if (x % kPri[i] == 0) factor_cnt[i] += sign;
      }
    }
    LL arr_mp[kN][kN];
    LL MultiP(int a, int b) {
      if (arr_mp[a][b] != -1) {
    #if __MODE__ == 1 || __MODE__ == 2
        TAB printf("MultiP(%d, %d) = %lld\n", a, b, arr_mp[a][b]);
    #endif
        return arr_mp[a][b];
      }
      int n = a + b;
      for (int i = 1; i <= n; i++) FactorDecom(i, 1);
      for (int i = 1; i <= a; i++) FactorDecom(i, -1);
      for (int i = 1; i <= b; i++) FactorDecom(i, -1);
      LL ans = 0;
      for (int i = 0; i < kPriCnt; i++) {
        while (factor_cnt[i]--) ans = ans * kPri[i] % kMod;
      }
    #if __MODE__ == 1 || __MODE__ == 2
      TAB printf("MultiP(%d, %d) = %lld\n", a, b, ans);
    #endif
      return arr_mp[a][b] = ans;
    }
    // Var
    int n0, a0, b0, c0;
    // Combination
    LL arr_c[kN * 3][kN * 3];
    LL C(int n, int m) {
      if (m == 0 || m == n) {
        return 1;
      } else if (arr_c[n][m] != -1) {
        return arr_c[n][m];
      } else {
        return arr_c[n][m] = (C(n - 1, m) + C(n - 1, m - 1)) % kMod;
      }
    }
    // DP
    LL arr_s[kN][kN * 3][kN * 3];
    LL S(int i, int cnt, int h) {
      FUNC_IN("S(%d, %d, %d)\n", i, cnt, h);
      if (cnt < h) {
        FUNC_OUT("S(%d, %d, %d) = %d (Judgement: cnt < h)\n", i, cnt, h,
                 0);
        return 0;
      } else if (i == 1) {
        FUNC_OUT("S(%d, %d, %d) = %d (Judgement: i == 1)\n", i, cnt, h,
                 cnt == h);
        return cnt == h;
      } else if (arr_s[i][cnt][h] != -1) {
        FUNC_OUT("S(%d, %d, %d) = %lld (Memory)\n", i, cnt, h,
                 arr_s[i][cnt][h]);
        return arr_s[i][cnt][h];
      } else {
        LL ans = 0;
        // This = h
        for (int h2 = 0; h2 < h; h2++) {
          ans = (ans + S(i - 1, cnt - h, h2)) % kMod;
        }
        // This = h2
        for (int h2 = 0; h2 <= h; h2++) {
          ans = (ans + S(i - 1, cnt - h2, h)) % kMod;
        }
        FUNC_OUT("S(%d, %d, %d) = %lld (Calculation)\n", i, cnt, h,
                 ans);
        return arr_s[i][cnt][h] = ans;
      }
    }
    LL arr_g[kN][kN * 2];
    LL G(int a, int bc) {
      FUNC_IN("G(%d, %d)\n", a, bc);
      if (arr_g[a][bc] != -1) {
        FUNC_OUT("G(%d, %d) = %lld (Memory)\n", a, bc,
                 arr_g[a][bc]);
        return arr_g[a][bc];
      } else {
        LL ans = 0;
        for (int h = 0; h <= a; h++) {
          ans = (ans + S(n0, a + bc, h) * C(a + bc - h, a - h) % kMod) % kMod;
        }
        FUNC_OUT("G(%d, %d) = %lld (Calculation)\n", a, bc,
                 ans);
        return arr_g[a][bc] = ans;
      }
    }
    LL arr_f[kN][kN][kN * 2];
    LL F(int n, int a, int bc) {
      FUNC_IN("F(%d, %d, %d)\n", n, a, bc);
      if (n == 1) {
        LL ans = G(a, bc);
        FUNC_OUT("F(%d, %d, %d) = %lld (Judgement)\n", n, a, bc,
                 ans);
        return ans;
      } else if (arr_f[n][a][bc] != -1) {
        FUNC_OUT("F(%d, %d, %d) = %lld (Memory)\n", n, a, bc,
                 arr_f[n][a][bc]);
        return arr_f[n][a][bc];
      } else {
        LL ans = 0;
        for (int a2 = 0; a2 <= a; a2++) {
          for (int bc2 = 0; bc2 <= bc; bc2++) {
            LL f = F(n - 1, a - a2, bc - bc2);
            LL g = G(a2, bc2);
            ans = (ans + f * g % kMod) % kMod;
          }
        }
        FUNC_OUT("F(%d, %d, %d) = %lld (Calculation)\n", n, a, bc,
                 ans);
        return arr_f[n][a][bc] = ans;
      }
    }
    int T;
    int main() {
    #if __MODE__ == 0
      printf("longlongzhu123:\n");
    #endif
    #if __MODE__ == 3 || __MODE__ == 2
      freopen("2806.in", "r", stdin);
      freopen("2806.out", "w", stdout);
    #endif
      memset(arr_c, -1, sizeof(arr_c));
      memset(arr_mp, -1, sizeof(arr_mp));
      scanf("%d", &T);
      while (T--) {
        memset(arr_s, -1, sizeof(arr_s));
        memset(arr_g, -1, sizeof(arr_g));
        memset(arr_f, -1, sizeof(arr_f));
        scanf("%d %d %d %d", &a0, &b0, &c0, &n0);
        LL ans = 0;
        ans = (ans + F(n0, a0, b0 + c0) * C(b0 + c0, c0) % kMod) % kMod;
        ans = (ans + F(n0, b0, a0 + c0) * C(a0 + c0, a0) % kMod) % kMod;
        ans = (ans + F(n0, c0, a0 + b0) * C(a0 + b0, a0) % kMod) % kMod;
        printf("%lld\n", ans);
      }
      return 0;
    }
    
  • 相关阅读:
    Vue 从入门到进阶之路(十)
    Vue 从入门到进阶之路(九)
    MySQL 小记
    Vue 从入门到进阶之路(八)
    Vue 从入门到进阶之路(七)
    Vue 从入门到进阶之路(六)
    Vue 从入门到进阶之路(五)
    MongoDB 小记
    Vue 从入门到进阶之路(四)
    Vue 从入门到进阶之路(三)
  • 原文地址:https://www.cnblogs.com/longlongzhu123/p/10538437.html
Copyright © 2011-2022 走看看