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 个为非法解
![](https://gitee.com/shulpt/figure/raw/master/img/20210102170053.png#pic_center)
- 1 ≤ S ≤ B ≤ 5000 1 \le S \le B \le 5000 1≤S≤B≤5000
题解:
解法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 3∗dp[1][2]=3 | 2 ∗ dp [ 2 ] [ 1 ] = 4 2*\text{dp}[2][1]=4 2∗dp[2][1]=4 | 1 ∗ dp [ 3 ] [ 0 ] = 1 1*\text{dp}[3][0]=1 1∗dp[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 3∗dp[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]=3∗dp[1][2]+2∗dp[2][1]+1∗dp[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]=i∗dp[1][j−1]+(i−1)∗dp[2][j−2]+⋯+1∗dp[i][j−i]dp[i][j]=i∗dp[1][j−1]+(i−1)∗dp[2][j−2]+⋯+(i−j+1)dp[i][0]j>ij≤i
观察转移方程:
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=1时,⎩⎪⎪⎪⎨⎪⎪⎪⎧dp[1][0]=1dp[1][1]=1∗dp[1][0]=1dp[1][2]=1∗dp[1][1]=1dp[1][3]=1∗dp[1][2]=1S=2时,⎩⎪⎪⎪⎨⎪⎪⎪⎧dp[2][0]=1dp[2][1]=2∗dp[1][0]=2dp[2][2]=2∗dp[1][1]+dp[2][0]=3dp[2][3]=2∗dp[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]=2∗dp[1][1]+1∗dp[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]=2∗dp[1][1]+1∗dp[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
i−1 时递推式的答案,相加后正好相当于
i
−
1
i-1
i−1 时的递推式系数+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
i−1 列的划分结果下,再添加 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]=(2∗dp[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列:
![](https://gitee.com/shulpt/figure/raw/master/img/20210102170139.png#pic_center)
共有 8 种方法,但有 1 种重复。观察重复的两个,实际上就相当于在 dp[1][4] \text{dp[1][4]} dp[1][4] 的方案下分别向最左侧和最右侧各加一列只有1个,即
![](https://gitee.com/shulpt/figure/raw/master/img/20210102170142.png#pic_center)
可以发现,因为我们每次添加的是仅有 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]=2∗dp[i-1][j-1]−dp[i-2][j-2]
再考虑将整体抬高 1 格,即
![](https://gitee.com/shulpt/figure/raw/master/img/20210102170145.png#pic_center)
即
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];
}
2019-2020 ACM-ICPC Latin American Regional Programming Contest (https://codeforces.ml/gym/102428) ↩︎