ZOJ 2955 Interesting Dart Game 鸽笼原理

Recently, Dearboy buys a dart for his dormitory, but neither Dearboy nor his roommate knows how to play it. So they decide to make a new rule in the dormitory, which goes as follows:
Given a number N, the person whose scores accumulate exactly to N by the fewest times wins the game.
Notice once the scores accumulate to more than N, one loses the game.
Now they want to know the fewest times to get the score N.

So the task is :
Given all possible dart scores that a player can get one time and N, you are required to calculate the fewest times to get the exact score N.
Input

Standard input will contain multiple test cases. The first line of the input is a single integer T (1 <= T <= 50) which is the number of test cases. And it will be followed by T consecutive test cases.
Each test case begins with two positive integers M(the number of all possible dart scores that a player can get one time) and N. Then the following M integers are the exact possible scores in the next line.
Notice: M (0 < M < 100), N (1 < N <= 1000000000), every possible score is (0, 100).

Output

For each test case, print out an integer representing the fewest times to get the exact score N.
If the score can’t be reached, just print -1 in a line.

Sample Input

3
3 6
1 2 3
3 12
5 1 4
1 3
2

Sample Output

2
3
-1

思路:鸽笼原理!
鸽笼原理举例:若数组a[i]的前缀和为sum[i],则一定存在0<=s<t<=m,使得sum[t]-sum[s]可以被m整除。
证明:对于属于1到m中的任意一个数i,sum[i]取余m的结果为1,2,3…m-1这m-1个可能。而sum[1]到sum[m]取余m会有m个结果,根据鸽笼原理,这m个结果最多有m-1个可能,所有一定至少有两个数的sum取余m相等,设这两个数为s和t,因为s!=t,因为s和t无大小之分,所有就假设s<t,就出来了(sum[t]-sum[s])%m=0,因为他们取余m的余数都相等,所有就是这个结果了。所有这个结论就证明出来了。

题意:在这个题可以化简成给了最多100个数,每个数可以选无数次,求总和正好达到所给n的最小选择数。
应用:鸽笼原理在本题中的应用为将n变小。假设给的m个数为a[i]数组,先给这个数组由小到大排序,然后得到一个很神奇的结论,就是前m-1个数选取的次数之和小于a[m],即假设k1a1+k2a2+…+k[m-1]*a[m-1]+k[m]*a[m]=n,k[i]代表选了多少次a[i]。
这个结论就成了k1+k2+…+k[m-1]的和小于a[m]。数学博大精深wc!
反证法证明若k1+k2+…+k[m-1]>=a[m],则根据鸽笼定理(例题证明)则存在k序列的某一连续段之和能被a[m]整除,因此这一段肯定可以被换成a[m]那么就不会是最优解了,所以证出了那个神奇的结论。而本题其实就是个完全背包问题,因为n特别特别大所有不能直接做,那就可以利用这个鸽笼原理将这个n变小,对于前m-1个数选择的最大价值就是a[m]*a[m]了,因此可以将n对a[m]*a[m]取余n就小了。

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
int dp[10100],a[110];
int main(void){
 int T,m,n,i,j;
 cin>>T;
 while(T--){
  scanf("%d%d",&m,&n);
  memset(dp,-1,sizeof(dp));
  for(i=0;i<m;i++)
  scanf("%d",&a[i]);
  sort(a,a+m);
  int ans=0;
  ans=n/(a[m-1]*a[m-1])*a[m-1];
  n=n%(a[m-1]*a[m-1]);
  dp[0]=0;
  for(i=0;i<m;i++){
   for(j=a[i];j<=n;j++){
    if(dp[j-a[i]]!=-1){
     if(dp[j]!=-1)
     dp[j]=min(dp[j],dp[j-a[i]]+1);
     else
     dp[j]=dp[j-a[i]]+1;
    }
   }
  }
  ans+=dp[n];
  //一开始我直接输出ans,但是没有考虑其实就算你把n变小了,如果还是合成不了n的话,你还是要输出-1!!!!
  if(dp[n]==-1)
  printf("-1\n");
  else
  cout<<ans<<"\n";
 }
 return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值