3000ms 256000K
Last feast the young princess received way too many coins. Since she is very young, she doesn’t know thevalue of each coin, if you give her a coin with the value 5 or a coin with the value 1, she will considerthem both as just 1 coin, regardless of the value.
However, she can notice that the coin with value 5 doesn’t look the same as the coin with value 1, andshe will be happy if she has the same number of coins of each value. Otherwise, she will not be happy.She has a lot of coins of different values, and she needs some subset of these coins such that the sum oftheir values should be exactly S, and the number of coins of each value in this subset should be the same.Can you help her to count the number of different ways to do this?
Input
Your program will be tested on one or more test cases. The first line of the input will be a single integerT (1 ≤ T ≤ 100) representing the number of test cases. Followed by T test cases. Each test case startswith a line containing two integers separated by a single space S and N (1 ≤ S ≤ 5,000) (1 ≤ N ≤ 50)representing the total required sum and the number of different values of coins, respectively. Followed byN lines, each one will contain two integers separated by a single space Vi and Ci (1 ≤ Vi, Ci ≤ 5,000)representing the value of a coin and the number of coins the princess has with this value, respectively. Foreach test case, all values of Vi will be distinct.
Output
For each test case print a single line containing “Case n:” (without quotes) where n is the test case number(starting from 1) followed by a space then the number of different ways to make the total sum S asdescribed above. Two ways are considered different if any coin value does not appear the same number oftimes in both ways.
You can assume that the result will always fit in a 64-bit signed integer.
输出时每行末尾的多余空格,不影响答案正确性
样例输入
2
10 2
2 2
6 1
10 4
1 10
2 10
3 10
4 10
样例输出
Case 1: 0
Case 2: 5
样例解释
In the first test case, the only way to make the sum 10, is to use the following subset of coins (2, 2, 6),but this isn’t valid because there are 2 coins with value 2 and 1 coin with value 6. In the second test case, the following are the 5 different ways: (1, 1, 1, 1, 1, 1, 1, 1, 1, 1), (2, 2, 2, 2, 2),(2, 2, 3, 3), (1, 1, 4, 4), (1, 2, 3, 4).
题意:
有N种硬币,每种硬币值Vi,个数是Ci,问多少种硬币的选择的方案使得总价钱是S,而且每种硬币选中个数一样。
分析
假设现在有总价钱S, 有一种选择方案是选择了价值为a,b,c,d的四种硬币,并且每一枚硬币选择的个数为x, 则我们有 ax+bx+cx+dx = S <-> (a + b + c + d) * x = S, 我们不难发现,由于硬币的价值都为整数,那么x则为S的因子。那么我们可以将问题转换为:在N种硬币中选择若干个硬币使得它们的价值和为 S/x(x为S的因子),并且每一种硬币我们都有选和不选两种状态,这样我们就将问题转换为了01背包问题,我们依次枚举选择的硬币的个数,并对每一种个数进行01背包求解,并把每一次枚举的答案累加即可,需要注意的一点就是答案可能会爆int。
dp[j]维护的是 当价值为j时,所有的可能组合的方案, 需要注意的一点就是当价值为0时,也就是dp[0]也有一种选择方案,那就是一个硬币都不选,所以 dp[0] = 1
AC代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <limits.h>
typedef long long int ll;
const int maxn = 5e3 + 7;
using namespace std;
ll dp[maxn];
int vi[maxn], ci[maxn];
int main(void)
{
int T, Case = 0;
cin >> T;
while(T--)
{
Case ++;
// 记录答案
ll ans = 0;
int S, N, maxCi = INT_MIN;
cin >> S >> N;
for(int i = 1; i <= N; i ++ )
{
cin >> vi[i] >> ci[i];
// 这里我们求出所有硬币数量的最大值,
// 因为我们可以枚举的硬币的数量也就是所有硬币数量的最大值。
maxCi = max(maxCi, ci[i]);
}
for(int i = 1; i <= maxCi && i <= S; i ++ )
{
// 如果i是S的因子, 我们进行下一步的操作。
if(S%i == 0)
{
// 现在我们要从这些硬币中选择若干, 使得他们的价值为S/i;
int curVal = S/i;
memset(dp, 0, sizeof(dp));
// 当前价值为0 就是一个硬币也不选,这也是一种方案
dp[0] = 1;
for(int j = 1; j <= N; j ++ )
{
// 当前我们枚举的是所有的硬币选i个, 硬币数量不够i个的自然不选。
if(ci[j] >= i)
{
/*
这个就是一个普通的01背包, 但是这里我们需要维护的是
dp[k] 是价值为k的时候可以组合的方案的个数, 这里的状态转移方程
有点递归的意思, 比如当前价值k = 10, 硬币的面值是2, 你要求k = 10的
你得先求出k = 8的, 你要求k = 8的, 你得先求出k = 6的,你要求k = 6的,
你得先求出k = 4的, 你要求k = 4的, 你得先求出k = 2的,你要求k = 2的
你得先求出k = 0的, 就这样求出来之后每一个依次累加就是k = 10的最佳答案。
*/
for(int k = curVal; k >= vi[j]; k -- )
dp[k] += dp[k - vi[j]];
}
}
ans += dp[curVal];
}
}
cout<<"Case "<< Case << ": " << ans;
if(T) puts("");
}
return 0;
}