在网友的国度中共有 n n n 种不同面额的货币,第 i i i 种货币的面额为 a [ i ] a[i] a[i],你可以假设每一种货币都有无穷多张。为了方便,我们把货币种数为 n n n、面额数组为 a [ 1.. n ] a[1..n] a[1..n] 的货币系统记作 ( n , a ) (n,a) (n,a)。
在一个完善的货币系统中,每一个非负整数的金额 x x x 都应该可以被表示出,即对每一个非负整数 x x x,都存在 n n n 个非负整数 t [ i ] t[i] t[i] 满足 a [ i ] × t [ i ] a[i] \times t[i] a[i]×t[i] 的和为 x x x。然而, 在网友的国度中,货币系统可能是不完善的,即可能存在金额 x x x 不能被该货币系统表示出。例如在货币系统 n = 3 n=3 n=3, a = [ 2 , 5 , 9 ] a=[2,5,9] a=[2,5,9] 中,金额 1 , 3 1,3 1,3 就无法被表示出来。
两个货币系统 ( n , a ) (n,a) (n,a) 和 ( m , b ) (m,b) (m,b) 是等价的,当且仅当对于任意非负整数 x x x,它要么均可以被两个货币系统表出,要么不能被其中任何一个表出。
现在网友们打算简化一下货币系统。他们希望找到一个货币系统 ( m , b ) (m,b) (m,b),满足 ( m , b ) (m,b) (m,b) 与原来的货币系统 ( n , a ) (n,a) (n,a) 等价,且 m m m 尽可能的小。他们希望你来协助完成这个艰巨的任务:找到最小的 m m m。
输入文件的第一行包含一个整数 T T T,表示数据的组数。
接下来按照如下格式分别给出 T T T 组数据。 每组数据的第一行包含一个正整数 n n n。接下来一行包含 n n n 个由空格隔开的正整数 a [ i ] a[i] a[i]。
输出文件共有 T T T 行,对于每组数据,输出一行一个正整数,表示所有与 ( n , a ) (n,a) (n,a) 等价的货币系统 ( m , b ) (m,b) (m,b) 中,最小的 m m m。
2
4
3 19 10 6
5
11 29 13 19 17
2
5
在第一组数据中,货币系统 ( 2 , [ 3 , 10 ] ) (2, [3,10]) (2,[3,10]) 和给出的货币系统 ( n , a ) (n, a) (n,a) 等价,并可以验证不存在 m < 2 m < 2 m<2 的等价的货币系统,因此答案为 2 2 2。 在第二组数据中,可以验证不存在 m < n m < n m<n 的等价的货币系统,因此答案为 5 5 5。
【数据范围与约定】
对于 100 % 100\% 100% 的数据,满足 1 ≤ T ≤ 20 , n , a [ i ] ≥ 1 1 ≤ T ≤ 20, n,a[i] ≥ 1 1≤T≤20,n,a[i]≥1。
给定的货币系统 ( n , a ) (n, a) (n,a),判断在 a a a中有哪些面额可以用其他的面额凑出来,删除掉这些面额后,剩下的面额数量即为最小的 m m m。
这个题目本质上是一个完全背包。先将 a a a中面额从小到大排序。设状态dp[i][j]
表示:用前i
种面额,凑出金额j
的所有方案中,用到最多的货币数。显然,如果一个货币面额j
,其dp[n][j] > 1
,则该面额可以被其他货币凑出;如果dp[n][j] == 1
,则该面额不能被其他货币凑出。容易得到,状态转移方程为:
d p [ i ] [ j ] = m a x ( d p [ i − 1 ] [ j ] , d p [ i − 1 ] [ j − a [ i ] ] + 1 ) dp[i][j] = {\rm{max}}(dp[i-1][j], dp[i-1][j-a[i]]+1) dp[i][j]=max(dp[i−1][j],dp[i−1][j−a[i]]+1)
注意边界条件:
d p [ 0 ] [ j ] = { − i n f , j ≠ 0 0 , j = 0 dp[0][j] = \begin{cases} -inf, &j\ne 0\\ 0, &j=0 \end{cases} dp[0][j]={−inf,0,j=0j=0
确保所有符合条件的状态都由 dp[0][0]
转移而来。
#include
using namespace std;
const int maxn = 105;
int T, n;
int M;
int a[maxn];
//滚动数组
int dp[25005];
int ans;
int main(){
cin>>T;
while(T--){
cin>>n;
M = -1;
ans = 0;
for(int i=1;i<=n;i++){
cin>>a[i];
M = max(M, a[i]);
}
//边界条件
memset(dp, 0x80, sizeof(dp));
dp[0] = 0;
for(int i=1;i<=n;i++){
for(int j=a[i];j<=M;j++){
dp[j] = max(dp[j], dp[j-a[i]]+1);
}
}
for(int i=1;i<=n;i++){
//统计不能凑出的货币面额的数量
if(dp[a[i]]==1) ans++;
}
cout<<ans<<'\n';
}
return 0;
}
老唐最近迷上了飞盘,约翰想和他一起玩,于是打算从他家的 N N N 头奶牛中选出一支队伍。
每只奶牛的能力为整数,第 i i i 头奶牛的能力为 R i R_i Ri 。飞盘队的队员数量不能少于 1 1 1、大于 N N N。一支队伍的总能力就是所有队员能力的总和。
约翰比较迷信,他的幸运数字是 F F F ,所以他要求队伍的总能力必须是 F F F 的倍数。请帮他
算一下,符合这个要求的队伍组合有多少?由于这个数字很大,只要输出答案对 1 0 8 10^8 108 取模的值。
第一行:两个用空格分开的整数: N N N 和 F F F。
第二行到 N + 1 N+1 N+1 行:第 i + 1 i+1 i+1 行有一个整数 R i R_i Ri ,表示第 i i i 头奶牛的能力。
第一行:单个整数,表示方案数对 1 0 8 10^8 108 取模的值。
4 5
1
2
8
2
3
这道题实质上是01背包。定义状态dp[i][j]
表示:仅考虑前i
头牛,凑出总能力值在模F
的条件下为j
的方案数。显然,这道题目的最终答案对应了状态dp[N][0]
。由于我们在选择牛时,仅需要考虑总能力模F
下的值,所以我们可以在处理输入时,可以令所有的牛的能力值模F
。
状态转移方程为:
d p [ i ] [ j ] = d p [ i − 1 ] [ j ] + d p [ i − 1 ] [ j + F − a [ i ] ] dp[i][j] = dp[i-1][j] + dp[i-1][j+F-a[i]] dp[i][j]=dp[i−1][j]+dp[i−1][j+F−a[i]]
解释:对于我们当前遍历的牛i
,其在一个方案中:
dp[i-1][j]
,即用前i-1
头牛凑出模F
为j
的总能力值。dp[i-1][j+F-a[i]]
,即加上这头牛后,凑出模F
为j
的总能力值。对于边界条件,我们可以首先初始化第1
头牛的情形:
dp[1][0] = 1
。注意到,这种情形是不合理的,所以在最终的答案中需要减去。a[i]
,dp[1][a[i]] = 1#include
using namespace std;
const int p = 1e8;
const int maxn = 2005;
const int maxf = 1005;
int dp[2][maxf];
int N, F;
int a[maxn];
int cnt = 0;
int main(){
cin>>N>>F;
for(int i=1;i<=N;i++){
cin>>a[i];
a[i] = a[i]%F;
}
dp[cnt^1][a[1]] = 1;
dp[cnt^1][0] = 1;
for(int i=2;i<=N;i++){
for(int j=0;j<F;j++){
dp[cnt][j] = (dp[cnt^1][j]+dp[cnt^1][(j+F-a[i])%F])%p;
}
cnt = cnt^1;
}
cout<<dp[cnt^1][0]-1;
return 0;
}
卡门――农夫约翰极其珍视的一条 Holsteins
奶牛――已经落了到 “垃圾井” 中。“垃圾井” 是农夫们扔垃圾的地方,它的深度为 D D D( 2 ≤ D ≤ 100 2 \le D \le 100 2≤D≤100)英尺。
卡门想把垃圾堆起来,等到堆得高度大等于于井的深度时,她就能逃出井外了。另外,卡门可以通过吃一些垃圾来维持自己的生命。
每个垃圾都可以用来吃或堆放,并且堆放垃圾不用花费卡门的时间。
假设卡门预先知道了每个垃圾扔下的时间 t t t( 1 ≤ t ≤ 1000 1 \le t \le 1000 1≤t≤1000),以及每个垃圾堆放的高度 h h h( 1 ≤ h ≤ 25 1 \le h \le 25 1≤h≤25)和吃进该垃圾能维持生命的时间 f f f( 1 ≤ f ≤ 30 1 \le f \le 30 1≤f≤30),要求出卡门最早能逃出井外的时间,假设卡门当前体内有足够持续 10 10 10 小时的能量,如果卡门 10 10 10 小时内(不含 10 10 10 小时,维持生命的时间同)没有进食,卡门就将饿死。
第一行为两个整数, D D D 和 G G G( 1 ≤ G ≤ 100 1 \le G \le 100 1≤G≤100), G G G为被投入井的垃圾的数量。
第二到第 G + 1 G+1 G+1 行每行包括三个整数: T T T( 1 ≤ T ≤ 1000 1 \le T \le 1000 1≤T≤1000),表示垃圾被投进井中的时间; F F F( 1 ≤ F ≤ 30 1 \le F \le 30 1≤F≤30),表示该垃圾能维持卡门生命的时间;和 H H H( 1 ≤ H ≤ 25 1 \le H \le 25 1≤H≤25),该垃圾能垫高的高度。
如果卡门可以爬出陷阱,输出一个整数,表示最早什么时候可以爬出;否则输出卡门最长可以存活多长时间。
20 4
5 4 9
9 3 2
12 6 10
13 1 1
13
这道题可以用01背包的思路解决。定义状态dp[i][j]
表示:仅考虑前i
个垃圾,奶牛到达高度j
,最长的存活时间。
对于一个垃圾i
,我们有吃或不吃两种选择:
dp[i][j]
由状态dp[i-1][j]
转移而来。为了使这个转移合法,必须保证奶牛在状态dp[i-1][j]
的最长存活时间足够支撑它吃到垃圾i
即:dp[i][j]
由状态dp[i-1][j-trash[i].h]
转移而来。为了使这个转移合法,必须保证奶牛在状态dp[i-1][j-trash[i].h]
的最长存活时间足够支撑它吃到垃圾i
,且j-trash[i].h >= 0
,即:考虑初始状态:
d p [ 0 ] [ j ] = { 10 , j = 0 − i n f , j ≠ 0 dp[0][j]= \begin{cases} 10,&j=0\\ -inf,&j\ne0 \end{cases} dp[0][j]={10,−inf,j=0j=0
所以,我们可以将dp[][]
中的每个元素初始化为-inf
,然后再令dp[0][0]
为10
。
#include
using namespace std;
const int maxn = 105;
const int inf = 0x7f7f7f7f;
struct TRASH{
int t;
int f;
int h;
bool operator<(const TRASH& a)const{
return t<a.t;
}
};
int D, G;
TRASH trash[maxn];
int dp[maxn][maxn];
int maxh=-1, maxt=-1;
int ans = 0x7f7f7f7f;
int main(){
cin>>D>>G;
for(int i=1;i<=G;i++){
cin>>trash[i].t>>trash[i].f>>trash[i].h;
}
sort(trash+1, trash+1+G);
memset(dp, 0x80, sizeof(dp));
dp[0][0] = 10;
for(int i=1;i<=G;i++){
for(int j=0;j<=D;j++){
//吃
if(dp[i-1][j]>=trash[i].t){
dp[i][j] = max(dp[i][j], dp[i-1][j]+trash[i].f);
}
//不吃
if(j>=trash[i].h && dp[i-1][j-trash[i].h]>=trash[i].t){
dp[i][j] = max(dp[i][j], dp[i-1][j-trash[i].h]);
}
}
}
for(int i=1;i<=G;i++){
for(int j=0;j<=D;j++){
if(dp[i][j]>=0){
maxh = max(maxh, j);
if(maxh>=D){
ans = min(ans, trash[i].t);
}
}
maxt = max(maxt, dp[i][j]);
}
}
if(maxh>=D) cout<<ans;
else cout<<maxt;
return 0;
}