Gym102428F Fabricating Sculptures

Fabricating Sculptures1

题目

S S S 个方块,堆成 B B B 堆,要求每堆的个数 ≥ 1 \ge 1 1 并且最终的形状必须是单峰的(不能出现凹槽)

S = 6 ,   B = 3 S=6,\ B=3 S=6, B=3 的情况如下图,左边 8 8 8 个为可行解,右边 2 2 2 个为非法解

  • 1 ≤ S ≤ B ≤ 5000 1 \le S \le B \le 5000 1SB5000

题解:

解法1:前缀和优化

横向考虑,令 dp [ i ] [ j ] \text{dp}[i][j] dp[i][j] 表示:底层放了 i i i 个方块,上面再摆放 j j j 个方块的方案数。

观察 dp [ 3 ] [ 3 ] \text{dp}[3][3] dp[3][3] 的所有情况,即 S = 6 ,   B = 3 S=6,\ B=3 S=6, B=3 时的答案:

3 ∗ dp [ 1 ] [ 2 ] = 3 3*\text{dp}[1][2]=3 3dp[1][2]=3 2 ∗ dp [ 2 ] [ 1 ] = 4 2*\text{dp}[2][1]=4 2dp[2][1]=4 1 ∗ dp [ 3 ] [ 0 ] = 1 1*\text{dp}[3][0]=1 1dp[3][0]=1

dp [ 1 ] [ 2 ] \text{dp}[1][2] dp[1][2] 表示底层放 1 1 1 个,上面再放 2 2 2 个的方案数,此时只可能是 3 3 3 个方块放成 1 1 1 列,即 dp [ 1 ] [ 2 ] = 1 \text{dp}[1][2]=1 dp[1][2]=1

再考虑这种堆叠方式下的不同情况数,即将这 3 3 3 个块在底座上移动,共有 3 种情况,因此共有 3 ∗ dp [ 1 ] [ 2 ] = 3 3*\text{dp}[1][2]=3 3dp[1][2]=3 种方案数。同理,对于 dp [ 2 ] [ 1 ] \text{dp}[2][1] dp[2][1] 的情况,因为此时底层放了 2 2 2 个,所以可移动的情况只有 2 2 2 种;对于 dp [ 3 ] [ 0 ] \text{dp}[3][0] dp[3][0],无法移动,只有 1 1 1 种。

因此最终转移方程为: dp [ 3 ] [ 3 ] = 3 ∗ dp [ 1 ] [ 2 ] + 2 ∗ dp [ 2 ] [ 1 ] + 1 ∗ dp [ 3 ] [ 0 ] = 8 \text{dp}[3][3] = 3*\text{dp}[1][2]+2*\text{dp}[2][1]+1*\text{dp}[3][0]=8 dp[3][3]=3dp[1][2]+2dp[2][1]+1dp[3][0]=8

总结可得转移方程为:

{ dp [ i ] [ j ] = i ∗ dp [ 1 ] [ j − 1 ] + ( i − 1 ) ∗ dp [ 2 ] [ j − 2 ] + ⋯ + 1 ∗ dp [ i ] [ j − i ] j > i dp [ i ] [ j ] = i ∗ dp [ 1 ] [ j − 1 ] + ( i − 1 ) ∗ dp [ 2 ] [ j − 2 ] + ⋯ + ( i − j + 1 ) dp [ i ] [ 0 ] j ≤ i \begin{cases} \text{dp}[i][j]=i*\text{dp}[1][j-1]+(i-1)*\text{dp}[2][j-2]+\cdots+1*\text{dp}[i][j-i]&j>i\\\text{dp}[i][j]=i*\text{dp}[1][j-1]+(i-1)*\text{dp}[2][j-2]+\cdots+(i-j+1)\text{dp}[i][0]&j\le i\\ \end{cases} {dp[i][j]=idp[1][j1]+(i1)dp[2][j2]++1dp[i][ji]dp[i][j]=idp[1][j1]+(i1)dp[2][j2]++(ij+1)dp[i][0]j>iji

观察转移方程:

S = 1 时 , { dp [ 1 ] [ 0 ] = 1 dp [ 1 ] [ 1 ] = 1 ∗ dp [ 1 ] [ 0 ] = 1 dp [ 1 ] [ 2 ] = 1 ∗ dp [ 1 ] [ 1 ] = 1 dp [ 1 ] [ 3 ] = 1 ∗ dp [ 1 ] [ 2 ] = 1 S = 2 时 , { dp [ 2 ] [ 0 ] = 1 dp [ 2 ] [ 1 ] = 2 ∗ dp[1][0] = 2 dp [ 2 ] [ 2 ] = 2 ∗ dp[1][1] + dp [ 2 ] [ 0 ] = 3 dp [ 2 ] [ 3 ] = 2 ∗ dp[1][2] + dp [ 2 ] [ 1 ] = 4 \begin{aligned} &S=1时, \begin{cases} &\text{dp}[1][0] = 1\\ &\text{dp}[1][1] = 1*\text{dp}[1][0] = 1\\ &\text{dp}[1][2] = 1*\text{dp}[1][1] = 1\\ &\text{dp}[1][3] = 1*\text{dp}[1][2] = 1\\ \end{cases}\\ \\ &S=2时, \begin{cases} &\text{dp}[2][0] = 1\\ &\text{dp}[2][1] = 2*\textbf{dp[1][0]} = 2\\ &\text{dp}[2][2] = 2*\textbf{dp[1][1]}+\text{dp}[2][0] = 3\\ &\text{dp}[2][3] = 2*\textbf{dp[1][2]}+\text{dp}[2][1] = 4\\ \end{cases} \end{aligned} S=1dp[1][0]=1dp[1][1]=1dp[1][0]=1dp[1][2]=1dp[1][1]=1dp[1][3]=1dp[1][2]=1S=2dp[2][0]=1dp[2][1]=2dp[1][0]=2dp[2][2]=2dp[1][1]+dp[2][0]=3dp[2][3]=2dp[1][2]+dp[2][1]=4

发现:

  • 横向观察: dp [ i ] [ j ] \text{dp}[i][j] dp[i][j] 进行累加转移时,转移的状态的下标之和都等于 j j j(其实就对应总数为 j j j 时的摆放方案)
  • 纵向观察: dp [ i ] [ j ] \text{dp}[i][j] dp[i][j] 所加的项均在 dp [ i + 1 ] [ j ] \text{dp}[i+1][j] dp[i+1][j] 中出现,且系数+1

因此可以利用两个数组保存直接计算好的结果,优化复杂度。

  • sum[i] \text{sum[i]} sum[i]:总共用了 i i i 块时的方案数(如 sum [ 2 ] = dp [ 1 ] [ 1 ] + dp[2][0] \text{sum}[2]=\text{dp}[1][1]+\text{dp[2][0]} sum[2]=dp[1][1]+dp[2][0]
  • pre [ j ] \text{pre}[j] pre[j]:假设 i = 3 i=3 i=3 时, pre [ j ] \text{pre}[j] pre[j] 就是 dp [ 3 ] [ j ] \text{dp}[3][j] dp[3][j]

打表观察 pre, dp, sum \text{pre, dp, sum} pre, dp, sum 三个数组的值的变化

pre \text{pre} pre dp \text{dp} dp sum \text{sum} sum
pre [ 0 ] = 0 \text{pre}[0]=0 pre[0]=0 dp [ 1 ] [ 0 ] = 1 \text{dp}[1][0]=1 dp[1][0]=1 sum[1] = 1 \text{sum[1]}=1 sum[1]=1
pre[1] = 1 \text{pre[1]}=1 pre[1]=1 dp[1][1] = 1 \text{dp[1][1]}=1 dp[1][1]=1 sum[2] = 1 \text{sum[2]}=1 sum[2]=1
pre[2] = 1 \text{pre[2]}=1 pre[2]=1 dp[1][2] = 1 \text{dp[1][2]}=1 dp[1][2]=1 sum[3] = 1 \text{sum[3]}=1 sum[3]=1
pre[3] = 1 \text{pre[3]}=1 pre[3]=1 dp[1][3] = 1 \text{dp[1][3]}=1 dp[1][3]=1 sum[4] = 1 \text{sum[4]}=1 sum[4]=1
pre[0] = 0 \text{pre[0]}=0 pre[0]=0 dp[2][0] = 1 \text{dp[2][0]}=1 dp[2][0]=1 sum[2] = 2 \text{sum[2]}=2 sum[2]=2
pre[1] = pre[1]+sum[1] = 2 \text{pre[1]}=\text{pre[1]+sum[1]}=2 pre[1]=pre[1]+sum[1]=2 dp[2][1] = 2 \text{dp[2][1]}=2 dp[2][1]=2 sum[3] = 3 \text{sum[3]}=3 sum[3]=3
pre[2] = pre[2]+sum[2] = 3 \text{pre[2]}=\text{pre[2]+sum[2]}=3 pre[2]=pre[2]+sum[2]=3 dp[2][2] = 3 \text{dp[2][2]}=3 dp[2][2]=3 sum[4] = 4 \text{sum[4]}=4 sum[4]=4

按照转移方程, dp [ 2 ] [ 2 ] = 2 ∗ dp [ 1 ] [ 1 ] + 1 ∗ dp [ 2 ] [ 0 ] = pre[2]+sum[2] \text{dp}[2][2]=2*\text{dp}[1][1]+1*\text{dp}[2][0]=\text{pre[2]+sum[2]} dp[2][2]=2dp[1][1]+1dp[2][0]=pre[2]+sum[2]

此处的 pre [ 2 ] \text{pre}[2] pre[2] 是还未更新的,其代表的是之前的 dp [ 1 ] [ 1 ] \text{dp}[1][1] dp[1][1],而 sum[2] \text{sum[2]} sum[2] 代表的是 dp[1][1]+dp[2][0] \text{dp[1][1]+dp[2][0]} dp[1][1]+dp[2][0],因此
dp [ 2 ] [ 2 ] = pre[2]’ = pre[2]+sum[2] = dp[1][1] + dp[1][1]+dp[2][0] = 2 ∗ dp [ 1 ] [ 1 ] + 1 ∗ dp [ 2 ] [ 0 ] \begin{aligned} \text{dp}[2][2]=\text{pre[2]'} &= \text{pre[2]+sum[2]} \\ &= \text{dp[1][1]} + \text{dp[1][1]+dp[2][0]}\\ &= 2*\text{dp}[1][1]+1*\text{dp}[2][0] \end{aligned} dp[2][2]=pre[2]’=pre[2]+sum[2]=dp[1][1]+dp[1][1]+dp[2][0]=2dp[1][1]+1dp[2][0]
假设当前最底层放了 i i i 个,那么得出 dp[i][j] \text{dp[i][j]} dp[i][j] 后就能更新 sum[i+j] = sum[i+j]+dp[i][j] \text{sum[i+j]}=\text{sum[i+j]+dp[i][j]} sum[i+j]=sum[i+j]+dp[i][j]。然后进入 j+1 \text{j+1} j+1,将更新好的 sum[j+1] \text{sum[j+1]} sum[j+1] 加给 pre[j+1] \text{pre[j+1]} pre[j+1],因为 pre[j+1] \text{pre[j+1]} pre[j+1] 此时还存放的是 i − 1 i-1 i1 时递推式的答案,相加后正好相当于 i − 1 i-1 i1 时的递推式系数+1,并且加入了新的组合方法。

const int maxn = 5e3 + 10;
const int mod = 1e9 + 7;

int s, b;
int dp[maxn][maxn];
int sum[maxn], pre[maxn];

int main()
{
    cin >> s >> b;
    b -= s;
    for(int i = 1; i <= s; i++){
        for(int j = 0; j <= b; j++){
            (pre[j] += sum[j]) %= mod;
            if(j == 0) dp[i][j] = 1;
            else dp[i][j] = pre[j];
            (sum[i+j] += dp[i][j]) %= mod;
        }
    }
    cout << dp[s][b] << endl;
}

解法2:容斥原理

纵向考虑,令 dp[i][j] \text{dp[i][j]} dp[i][j] 表示:前 i i i 列,总共放了 j j j 个方块时的方案数

如果不考虑单峰,而是单调递增,那么该问题就等价于计数DP中最基础的整数划分问题,其递推式为:
dp[i][j] = dp[i-1][j-1] + dp[i][j-i] \text{dp[i][j]} = \text{dp[i-1][j-1]}+\text{dp[i][j-i]} dp[i][j]=dp[i-1][j-1]+dp[i][j-i]
在整数划分时,划分方案是一个递增的序列,在此基础上才保证了上面递推式得到的情况是唯一的。

对于本题,同样可以考虑在 i − 1 i-1 i1 列的划分结果下,再添加 1 列仅有 1 个方块的,但这 1 列既可以添加到最左边,也可以添加到最右边,这之中可能产生重复的情况,需要用容斥原理减去;或者是将所有的 i i i 列整体抬高 1 格。所以递推式如下:
dp[i][j] = ( 2 ∗ dp[i-1][j-1] − dp[i-2][j-2] ) + dp[i][j-i] \text{dp[i][j]} = (2*\text{dp[i-1][j-1]}-\text{dp[i-2][j-2]}) + \text{dp[i][j-i]} dp[i][j]=(2dp[i-1][j-1]dp[i-2][j-2])+dp[i][j-i]
同样观察 dp[3][6] \text{dp[3][6]} dp[3][6] 的计算过程:

首先考虑公式的前半部分, dp[2][5] \text{dp[2][5]} dp[2][5] dp[1][4] \text{dp[1][4]} dp[1][4] 的所有情况如下:

dp[2][5] \text{dp[2][5]} dp[2][5] dp[1][4] \text{dp[1][4]} dp[1][4]

考虑在 dp[2][5] \text{dp[2][5]} dp[2][5] 的基础上添加 1列:

共有 8 种方法,但有 1 种重复。观察重复的两个,实际上就相当于在 dp[1][4] \text{dp[1][4]} dp[1][4] 的方案下分别向最左侧和最右侧各加一列只有1个,即

可以发现,因为我们每次添加的是仅有 1 个方块的列, dp[2][5] \text{dp[2][5]} dp[2][5] 可以在 dp[1][4] \text{dp[1][4]} dp[1][4] 的基础上加 1 列,如果此时 dp[3][6] \text{dp[3][6]} dp[3][6] 再次选择在 dp[2][5] \text{dp[2][5]} dp[2][5] 的基础上加 1 列,就相当于在 dp[1][4] \text{dp[1][4]} dp[1][4] 的基础上连续加了 2 列,此时先放左再放右和先放右再放左就产生了重复,因此需要减去一遍重复的,即

dp[i][j] = 2 ∗ dp[i-1][j-1] − dp[i-2][j-2] \text{dp[i][j]} = 2*\text{dp[i-1][j-1]} - \text{dp[i-2][j-2]} dp[i][j]=2dp[i-1][j-1]dp[i-2][j-2]
再考虑将整体抬高 1 格,即


dp[i][j] = dp[i][j-i] \text{dp[i][j]} = \text{dp[i][j-i]} dp[i][j]=dp[i][j-i]

const int mod=1e9+7;
const int maxn=5005;

ll dp[maxn][maxn];

int main()
{
    int s,b; cin>>s>>b;
    dp[0][0]=1;
    for(int i = 1; i <= s; i++){
        for(int j = 1; j <= b; j++){
            if(i >= 2 && j >= 2) dp[i][j] = (2 * dp[i-1][j-1] - dp[i-2][j-2] + mod) % mod;
            else dp[i][j] = dp[i-1][j-1];
            if(j >= i) dp[i][j] = (dp[i][j] + dp[i][j-i]) % mod;
        }
    }
    cout<<dp[s][b];
}

  1. 2019-2020 ACM-ICPC Latin American Regional Programming Contest (https://codeforces.ml/gym/102428) ↩︎

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值