原题链接
https://pintia.cn/problem-sets/994805148990160896/exam/problems/1621700638587564032
题目大意
有 a a a 个红珠子和 b b b 个蓝珠子,我们要用其中的一部分珠子串成长度为 n n n的串,串的时候要求第 i i i 步要选 i i i 个同一种颜色的珠子串到串里面,问最后一共有多少种串法。
数据范围
0
⩽
n
⩽
2
×
1
0
5
0\leqslant n \leqslant 2 \times 10^5
0⩽n⩽2×105
0
⩽
a
⩽
1
0
5
0\leqslant a \leqslant 10^5
0⩽a⩽105
0
⩽
b
⩽
1
0
5
0\leqslant b \leqslant 10^5
0⩽b⩽105
输入样例
输入三个整数 n , a , b n,a,b n,a,b
10 4 7
输出样例
4
四种情况分别如下图所示:
题解
yy老师的题还是比较善良的,这道题要比前面很多动态规划的题容易一些。
对题目做一些转化,这个转化就是思考的过程:
(1)首先考虑经过几轮可以得到长度为
n
n
n 的珠子。假设一共经过
t
t
t 轮可以获得长度为
n
n
n 的珠子,第
i
i
i 轮添加
i
i
i 个珠子,则显然有公式
∑
i
=
1
t
i
=
(
t
2
+
t
)
/
2
\sum_{i=1}^t i = (t^2+t)/2
∑i=1ti=(t2+t)/2,反推出
t
=
(
−
1
+
1
+
8
n
)
/
2
t = (-1 + \sqrt{1+8n})/2
t=(−1+1+8n)/2,也就是说即便
n
n
n 取最大值,也可以在1265轮之内完成,即
t
⩽
1265
t\leqslant1265
t⩽1265;
(2)由于每一轮放珠子的位置是固定的,因此本题可以转化为:在
t
t
t 轮中选一部分轮次放红色珠子,剩下的全部放蓝色珠子,因此我们只需要选将红色珠子放在哪些轮,其他轮放蓝色珠子就好了;
(3)由于可以选
0
0
0 到
m
i
n
(
a
,
n
)
min(a,n)
min(a,n) 个红色珠子,而且每一步要选
i
i
i 个珠子,所以问题又转化为将
0
0
0 ~
a
a
a 的所有数分解为一系列不同的数字,数字的个数必然不会超过
t
t
t 个。
(4)根据以上分析,问题转化为了在
1
1
1 到
t
t
t 这些数字里不重复地选若干个,让它们的和不超过
a
a
a,问方案个数,这明显是一个01背包的方案问题,至此就彻底将问题转化成了我们熟悉的问题。
转移方程
根据以上思考,自然地得到了转移方程。这里再重新解释一下:第 i i i轮要选择是否将体积为 i i i的物品放入背包。用 f [ i ] [ j ] f[i][j] f[i][j]表示第 i i i轮容量为 j j j的背包可以放置的方案数,如果不放入新的物品,此时的方案数为 f [ i − 1 ] [ j ] f[i-1][j] f[i−1][j],如果放入体积为 i i i的物品,方案数增加 f [ i − 1 ] [ j − i ] f[i-1][j-i] f[i−1][j−i]。
for(int i=1;i<=t;i++) //每一件的价值是i
for(int j=0;j<=a;j++){ //背包容量从0到a
f[i][j] = f[i-1][j];
if(j>=i) f[i][j] += f[i-1][j-i];
f[i][j] %= mod;
}
最后要保证蓝色珠子的个数足够,也就是说在合理的方案下,装红色珠子背包占用的容量不能低于
m
a
x
(
n
−
b
,
0
)
max(n-b,0)
max(n−b,0),否则剩余的蓝色珠子不足以填满空格。所以最后的答案中背包的容量是从
m
a
x
(
n
−
b
,
0
)
max(n-b,0)
max(n−b,0) 到
a
a
a ,也就是
∑
i
=
m
a
x
(
n
−
b
,
0
)
b
f
[
t
]
[
i
]
\sum_{i=max(n-b,0)}^bf[t][i]
∑i=max(n−b,0)bf[t][i],求和即可。
需要注意的是,本题中由于背包容量可能达到200000,而轮次可能达到千轮,因此如果直接开一个二维数组可能导致内存超过题目限制,注意到转移方程只和上一轮的状态有关,所以可以用滚动数组优化。
AC代码
#include <bits/stdc++.h>
using namespace std;
const int mod = 1e9+7;
long long f[2][200050] = {0};
int main()
{
int n,a,b;
cin >> n >> a >> b;
double turns = (-1.0 + sqrt(1+8*n))/2;
if(abs(turns - (int)turns) >= 1e-6) {
cout << "0";
return 0;
}
f[0][0] = 1; //初始值保证了容量恰好为b的才能被统计进来
int t = turns;
for(int i=1;i<=t;i++){ //每一件的价值是i
for(int j=0;j<=b;j++){ //背包容量从0到b
f[i%2][j] = f[(i-1)%2][j];
if(j>=i) f[i%2][j] += f[(i-1)%2][j-i];
f[i%2][j] %= mod;
}
}
int start = max(0,n-a);
long long result = 0;
for(int i=start;i<=b;i++){
result += f[t%2][i];
result %= mod;
}
cout << result;
}