PAT T 1033 Strings of Red and Blue 题解 —— 01背包

文章介绍了一个关于珠子串法的问题,其中包含a个红珠子和b个蓝珠子,目标是组成长度为n的串,每步需选取i个同色珠子。通过动态规划和01背包的思想,将问题转化为在限定轮次内选择红色珠子的放置轮次,计算方案数。文章提供了转移方程和AC代码来解决这个问题。
摘要由CSDN通过智能技术生成

原题链接

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 0n2×105
0 ⩽ a ⩽ 1 0 5 0\leqslant a \leqslant 10^5 0a105
0 ⩽ b ⩽ 1 0 5 0\leqslant b \leqslant 10^5 0b105

输入样例

输入三个整数 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 t1265
(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[i1][j],如果放入体积为 i i i的物品,方案数增加 f [ i − 1 ] [ j − i ] f[i-1][j-i] f[i1][ji]

	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(nb,0),否则剩余的蓝色珠子不足以填满空格。所以最后的答案中背包的容量是从 m a x ( n − b , 0 ) max(n-b,0) max(nb,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(nb,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;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值