题解-建筑物

(〇)题目描述

有一个\(N\times N\)的单元格,在上面放置\(R\)个红色立方体,\(G\)个绿色立方体,\(B\)个蓝色立方体。要求从南向北看只能看见一种颜色的立方体(如图),求有多少种放法。\(N,R,G,B\leq25\)

图1

(一)解题思路

这道题是一道计数类的问题,第一感觉应该就是\(DP\)了。

下面来分析一下\(DP\)是否可行。

首先,要确定\(DP\)的阶段。因为题目要求从南向北看要全是单一的颜色,那么我们可以将其分为三大类:

  1. 看到的都是红色
  2. 看到的都是绿色
  3. 看到的都是蓝色

不难发现,上述三类的做法其实都是一样的,那么我们只需要考虑一类的做法即可。

就拿第一类来考虑。

不难发现,单元格每一行其实都是独立的。也就是说,只需要保证每一行从南向北看都是红色就可以了。既然如此,\(DP\)的阶段也就确定了:以每行为阶段

再考虑状态转移方程:

\(f(i,R,G,B)\)表示前\(i\)行放\(R\)个红色,\(G\)个绿色,\(B\)个蓝色的方案数。

再不妨预处理一个\(g(R,G,B)\)数组,表示在一行内,放\(R\)个红色,\(G\)个绿色,\(B\)个蓝色的方案数。

不难想到状态转移方程:
\[ f(i,R,G,B)=\sum f(i-1,R-R',G-G',B-B')\times g(R',G',B') \]
解释一下\(R'\)\(G'\)\(B'\)

其实就是在枚举第\(i\)行放\(R'\)个红色,\(G'\)个绿色,\(B'\)个蓝色

接下来问题就转换为了:如何求\(g(R,G,B)\)

显然,\(g(R,G,B)\) 是无法直接计算出来的,我们还需要一个\(DP\)

因为\(g(R,G,B)\)表示的是一行内\(R\)个红色,\(G\)个绿色,\(B\)个蓝色的方案数如图。

1392053-20190316173806831-1260225980.png

不难发现,如果从南向北看到的都是红色,将这些红色方块全都平移到第一列去,那么会有一个高度为\(h\)的红色立方体,而这个\(h\)刚好是整行立方体最高的高度,其他任何颜色的立方体都不能超过这个高度。

于是,不妨设\(s(i,r,g,b,h)\)表示(一行中)前\(i\)列放\(r\)个红色,\(g\)个绿色,\(b\)个蓝色的方案数。

首先我们要枚举当前这列放多少个立方体(假设放\(r'\)个红,\(g'\)个绿,\(b'\)个蓝)

我们还需要枚举前\(i-1\)列中最高立方体的高度(假设为\(h\))

然后,状态转移方程也不难推出:
\[ s(i,r,g,b,\max(r'+g'+b',h))=\sum s(i-1,r-r',g-g',b-b',h)\times P(r'-x,g',b') \]
其中\(x\)指的是第\(i\)列超过前\(i-1\)列的最高高度的方块数,没有超过则为\(0\)

\(P(r,g,b)\)指的是红色\(r\)个,绿色\(g\)个,蓝色\(b\)个摆成一“栋”的所有方案数。即对\(r,g,b\)错排。

然后再求\(g(R,G,B)\)就容易多了。状态转移方程:
\[ g(R,G,B)=\sum\limits_{h=0}^{R}s(N,R,G,B,h) \]
就这样我们可以\(DP\)求出方案数。但是(凡事离不开但是),时间复杂度好像有些问题......

我们来算一算:

  1. \(f(i,R,G,B)\)不仅要枚举\(i,R,G,B\)还要枚举\(R',G',B'\),复杂度:\(\Theta(N^7)\)
  2. \(s(i,r,g,b,h)\)要枚举\(i,r,g,b,h\)还有\(r',g',b'\),复杂度:\(\Theta(N^8)\)
  3. \(g(R,G,B)\)要枚举\(R,G,B\)\(h\),复杂度:\(\Theta(N^4)\)
总时间复杂度:\(\Theta(N^8)\)

显然,超时了。接下来便考虑如何卡常优化\(DP\)

(二)解题方法

\(DP\)优化法宝之合并状态

时间复杂度:\(\Theta(N^6)\)

在(一)中讲过,可以将立方体分为三大类再分别考虑每一类的情况。

当我们分类讨论每一类的情况时,会发现其实方块可以粗略分为两类

  1. 从南向北允许看见的立方体
  2. 从南向北不允许看见的立方体

此时,再给每一类分个颜色,允许看见的是红色(\(red\)),不允许看见的是青色(\(cya\))。

因为我们将其他两类颜色混合了,所以最后答案需要乘上\(C_{G+B}^{G}\)\(C_{G+B}^B\)

状态状态转移方程:
\[ \begin{cases} s(i,r,c,\max(r'+c',h))=\sum s(i-1,r-r',c-c',h)\times C_{\min(r'+c',h)}^{c'} \\g(i,R,C)=\sum\limits_{h=0}^R s(N,R,C,h) \\f(i,R,C)=\sum f(i-1,R-R',C-C')\times g(i,R',C') \end{cases} \]
分析一下时间复杂度:

  1. 枚举\(i,r,c,h\)\(r',c'\)\(\Theta(N^6)\)
  2. 枚举\(i,R,C\)\(h\)\(\Theta(N^3)\)
  3. 枚举\(i,R,C\)\(R',C'\)\(\Theta(N^5)\)

但是光是这样还不够,还是可能会超时,因此我们需要卡常数

怎么卡呢,看时间复杂度最高的那条式子,我们发现其实ta不用每次都重新做一遍!!!

还有组合数\(C\)也可以用杨辉三角形预处理出来。

这样就可以过辣!\(celebrate!\)

还有一个坑点就是,因为c是由两种颜色混合的所以要开两倍空间!!!我就是因为这个被卡了好久。

上代码啦啦啦:

#include<bits/stdc++.h>
using namespace std;
const long long mod = 1000000007;
const int maxn = 30;
long long f[maxn][maxn][maxn*2], g[maxn][maxn*2], s[maxn][maxn][maxn*2][maxn], C[maxn*2][maxn*2];
long long solve(int R, int G, int B, int N)
{
    memset(g, 0, sizeof(g));
    memset(f, 0, sizeof(f));
    for(int i=0; i<=R; i++)
        for(int j=0; j<=G+B; j++)
            for(int h = 0; h <= i; h++) g[i][j] = (g[i][j] + s[N][i][j][h])%mod;
    for(int i = 0; i <= R; i++)
        for(int j = 0; j <= G+B; j++)
            f[1][i][j] = g[i][j];
    for(int i = 2; i <= N; i ++)
        for(int red = 0; red <= R; red ++)
            for(int r = 0; r <= red; r ++)
                for(int cya = 0; cya <= G+B; cya ++)
                    for(int c = 0; c <= cya; c ++)
                        f[i][red][cya] = (f[i][red][cya]+f[i-1][red-r][cya-c]%mod*g[r][c]%mod)%mod;
    return f[N][R][G+B]%mod*C[G+B][G]%mod;
}
int main()
{
    freopen("2806.in","r",stdin);
    freopen("2806.out","w",stdout);
    int T, R = 25, G = 25, B = 25, N = 25;
    C[0][0] = 1ll;
    for(int i=1; i<=56; i++) C[i][0] = C[i][i] = 1ll;
    for(int i=2; i<=56; i++)
        for(int j=1; j<i; j++)
            C[i][j] = (C[i-1][j] + C[i-1][j-1])%mod;
    for(int i = 0; i <= R; i ++) s[1][i][0][i] = 1ll;
    for(int i = 2; i <= N; i ++)
        for(int h = 0; h <= R; h++)
            for(int red = h; red <= R; red ++)
                for(int r = 0; red - r >= h; r ++)
                    for(int cya = 0; cya <= G+B; cya ++)
                        for(int c = 0; c <= min(cya, h); c ++)
                            s[i][red][cya][max(h, r+c)] = (s[i][red][cya][max(h, r+c)]+s[i-1][red-r][cya-c][h]%mod*C[min(h, r+c)][c]%mod)%mod;
    scanf("%d",&T);
    while(T--)
    {
        long long ans = 0ll;
        scanf("%d%d%d%d",&R,&G,&B,&N);
        ans = (ans + solve(R, G, B, N))%mod;
        ans = (ans + solve(G, B, R, N))%mod;
        ans = (ans + solve(B, R, G, N))%mod;
        printf("%lld\n",ans);
    }
    return 0;
}

转载于:https://www.cnblogs.com/GDOI2018/p/10543287.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值