确定硬币系统是否规范


48

出纳的算法是在最小的硬币数量是工作得很好大多数货币体系进行改变的算法。但是,像大多数贪婪算法一样,它并非没有缺陷。如果建立的货币系统恰好正确(或错误),那么某些值会使收银员算法无法找到最佳找零。

请看以下示例:

我们有4¢,3¢和1¢硬币。我们想赚6美分。

出纳员算法将首先选择尽可能多的最大硬币(开始时为4美分),然后减去并重复。这将产生1个4¢硬币和2个1¢硬币,总共3个硬币。

不幸的是,对于该算法,有一种方法只能用两个硬币(两个3¢硬币)赚取6¢。

对于所有整数值,找零系统将被认为是标准的,而Cashier算法将找到最佳硬币数量。

任务

您的任务是将系统当作代表硬币值的整数的无序容器或已排序的有序容器,如果系统输入为标准值且为假,则输出真实值。

您的程序应适用于可以创造任何价值的所有系统。(即所有系统都将有1美分的硬币)

这是代码高尔夫最少字节获胜的代码。

测试用例

此列表绝不是详尽无遗的,您的程序应适用于所有有效输入

1, 3, 4       -> 0
1, 5, 10, 25  -> 1
1, 6, 10, 25  -> 0
1, 2, 3       -> 1
1, 8, 17, 30  -> 0
1, 3, 8, 12   -> 0
1, 2, 8, 13   -> 0
1, 2, 4, 6, 8 -> 1

@Geobits不是在所有情况下,它意味着更多的差异日益或者从最小的硬币最大的平等
约尔格Hülsermann

@JörgHülsermann这也不够好。[1、6、13]的差异越来越大,但它在类似18(13 + 1 * 5而不是6 * 3)的情况下仍然失败。
Geobits

16
这些被称为规范硬币系统这篇简短的论文给出了多项式时间算法,用于检查硬币系统是否规范(尽管效率较低的方法可能更容易打高尔夫球)。一个有趣的测试用例是25, 9, 4, 1从中赚取了 37美分(来自此Math.SE帖子)-即使每个硬币都比较小的硬币之和大,但非贪婪25, 4, 4, 4胜过贪婪25, 9, 1, 1, 1
xnor

1
@xnor注意9, 4, 1-> 4, 4, 49, 1, 1, 1一个更严格的示例更好。
isaacg

Answers:


9

Haskell,94 87 82字节

f s=and[j i-2<j(i-x)|let j i=last$0:[1+j(i-x)|x<-s,x<i],i<-[1..2*last s],x<-s,x<i]

该解决方案通过定义一个功能j执行收银员的算法,并告诉我们收银员使用了多少枚硬币来工作。然后,我们假设列表中所有先前的数字都是标准的,那么我们最多检查列表中最大数字的两倍,即选择最大可能的硬币是正确的选择。

此解决方案假定输入已排序。

最多检查两次最大数目的证明就足够了:假设系统对于某个数目不是规范的i,并且让k列表中的最大数目不大于i。假设i >= 2k且该系统对于小于的所有数字都是规范的i

采取一些最佳的方法来制造i硬币,并假设它不包含硬币k。如果我们丢掉其中一个硬币,则新的硬币总和必须大于k和小于i-但收银员在该数字上的算法将使用该k硬币-因此,这组硬币可以用一组相等的硬币来代替包含硬币k,因此存在一组包含k该数字硬币的硬币i,并且通过归纳,出纳员的算法返回最佳选择。

这个论点确实表明,我们只需要检查两个最大元素的总和即可,但这要花更长的时间。

编辑:感谢ØrjanJohansen,减少了五个字节!


1
您可以使用let代替来保存一个字节where。您可以将其作为|let ...模式后卫f s,或者放在列表推导中。
与Orjan约翰森

1
的另外四个字节j i=last$0:[1+j(i-k)|k<-s,k<i]
与Orjan约翰森

5

Pyth,18 15字节

!x#eST.gsky_S*e

测试套件

另一种蛮力。首先,形成所有硬币集合,每个集合最多包含k个,其中k是最大的硬币,假定是最后一个硬币。我相信,只要有这样的配对,就足以构成两套具有相同总和的硬币,一个是贪婪的,另一个是较短的。

然后,我找到这样的一对,如下所示:

子集按大小增加的顺序生成,然后按字典顺序按输入中的位置生成。稳定地按收集的硬币分组硬币。每个硬币收集都是按降序生成的,因此,当且仅当贪婪解决方案最佳时,贪婪解决方案才是该组的第一个元素,而按字典顺序,它将成为该组的最后一个元素。因此,我们找到贪婪的解决方案,并根据组中的非零索引进行过滤。如果硬币组是规范的,这将过滤掉所有内容,因此我们在逻辑上简单地对结果和输出求反。

说明:

!x#eST.gsky_S*e
!x#eST.gsky_S*eQQ   Variable introduction.
                    Q = eval(input()) - sorted list of coins.
              eQ    Greatest coin in the list
             *  Q   Repeat that many times.
            S       Sort the coins
           _        Reverse, so we have the coins in descending order.
          y         Form all subsets, in increasing size then
                    decreasing lexicographic order.
      .gsk          Group by sum
 x#                 Filter by the index in the group of
   eST              The last element lexicographically (greedy solution).
!                   Logically negate.

很好-知道为什么它在herokuapp上挂了[1、2、4、6、8]并/opt/tryitonline/bin/pyth: line 5: 28070 Killed ... Exit code: 137在TIO 被杀死吗?内存不足?
乔纳森·艾伦

这将使用2 ^(num个硬币*最后一个硬币)字节的内存。因此,对于您的示例,为2 ^ 40。拥有TB级内存的机器并不多
isaacg

我以为可能是这样,算法的描述是有道理的,但我还没有计算出数字-这么快就这么庞大!
乔纳森·艾伦

5

PHP,323字节

与其他计数硬币的方式相同,直到数组中最后两个元素的总和

<?function t($g){rsort($g);$m=array_slice($g,1);for($y=1,$i=$g[0];$i<$g[0]+$m[0];$i++){$a=$b=$i;$p=0;$r=$s=[];while($a||$b){$o=$n=0;$g[$p]<=$a?$a-=$r[]=$g[$p]:$o=1;($m[$p]??1)<=$b?$b-=$s[]=$m[$p]:$n=1;$p+=$o*$n;}$y*=count($r)<=count($s);}return$y;}for($i=0,$t=1;++$i<count($a=$_GET[a]);)$t*=t(array_slice($a,0,$i+1));echo$t;

我认为是我最好和最长的答案> 370字节

我只给出了扩展版本,因为它的长度比我以前的答案更长

for($x=1,$n=0,$f=[];++$n<count($a)-1;){
$z=array_slice($a,0,$n+1);
$q=$a[$n]-$a[$n-1];
$i=array_fill(1,$c=max($a[$n+1]??1,11),"X");#$q*$a[$n]
$f=range($a[$n],$c,$q);

$f[]=2*$a[$n];
for($d=[$z[$n]],$j=0;$j<$n;){
   $f[]=$a[$n]+$d[]=$z[$n]-$z[$j++]; 
}

while($f){
    $i[$t=array_pop($f)]="T";
    foreach($d as $g)
    if(($l=$t+$g)<=$c)$f[]=$l;
}

foreach($i as$k=>$v){
    if(in_array($k,$z))$i[$k]="S";
}
#var_dump($i);
if($i[$a[$n+1]]=="X")$x*=0;
}
echo$x;

此答案的说明

在线版本

  1. 将数组中的全部设置为false == X

  2. 将您控制的数组中的所有数字设置为S

  3. 发现最后一个S与另一个S或0之间的差异

  4. 从数组的最后一个S开始

  5. 将所有数字设置为D,其中Last S +所有差异之一

  6. 从所有D开始

  7. 将“ T”设置为数组中的D值

  8. GOTO 5对所有发现的DI重复该操作,但实际上不是在代码中

  9. 如果数组中的下一项具有X,则为假情况,否则为True

片段3中的其他步骤之间存在差异。1和4之间是2 X,这意味着您需要在步骤5中添加第二个D。在这种情况下,这个值等于10时,所有情况都是正确的,到目前为止,我可以看到存在某种关系在差值和要控制的数组中的计数之间进行计算,以计算找到最后一个错误的情况之前需要得到多少D(第5步)。

您可以将最后一项的多个值直接设置为true。这些点可能会有所不同,以决定下一个值的硬币贪婪计数是否等于数组中最后一个值的整数倍。另一方面,你可以设置敌人

  1. 将第一个敌人定为1 + L

  2. 从这一点开始,添加数组中的每个值以设置下一个敌人

  3. 从最后一个敌人后藤2开始

如果您现在有了敌人和真实情况,那么计数增加的可能性就会增加,而D越大,该可能性就越大。

table{width:80%}
td,th{width:45%;border:1px solid blue;}
<table>
  <caption>Working [1,4]</caption>
<tr><th>Number</th><th>Status</th></tr>
<tr><td>1</td><td>S</td></tr>
<tr><td>2</td><td>X</td></tr>
<tr><td>3</td><td>X</td></tr>
<tr><td>4</td><td>S</td></tr>
<tr><td>5</td><td>X</td></tr>
<tr><td>6</td><td>X</td></tr>
<tr><td>7</td><td>D3</td></tr>
<tr><td>8</td><td>D4</td></tr>
<tr><td>9</td><td>X</td></tr>
<tr><td>10</td><td>D3D3</td></tr>
<tr><td>11</td><td>D4D3</td></tr>
<tr><td>12</td><td>D4D4</td></tr>
<tr><td>13</td><td>D3D3D3</td></tr>
<tr><td>14</td><td>D4D3D3</td></tr>
<tr><td>15</td><td>D4D4D4</td></tr>
<tr><td>16</td><td>D4D4D3</td></tr>
</table>
<ul>
  <li>S Number in Array</li>
  <li>D Start|End point TRUE sum Differences from last S</li>
  <li>X False</li>
  </ul>

加?Bytes Thank You @JonathanAllan给我错误的测试用例
262 Bytes 差点儿,但还不够好4个错误的测试用例

测试案例 [1,16,256]之前应为true,之后为false

<?for($q=[1],$i=0,$t=1,$w=[0,1];++$i<count($a=$_GET[v]);$w[]=$a[$i],$q[]=$m)($x=$a[$i]-$a[$i-1])>=($y=$a[$i-1]-$a[$i-2])&&((($x)%2)==(($m=(($a[$i]+$x)*$a[$i-1])%$a[$i])%2)&&$m>array_sum($q)||(($x)%2)==0&&(($a[$i]-$a[$i-2])*2%$y)==0||in_array($m,$w))?:$t=0;echo$t;

数组的升序

说明

for($q=[1],$i=0,$t=1,$w=[0,1] # $t true case $q array for modulos $w checke values in the array
;++$i<count($a=$_GET[v])   #before loop
;$w[]=$a[$i],$q[]=$m) # after loop $q get the modulo from the result and fill $w with the checked value

($x=$a[$i]-$a[$i-1])>=($y=$a[$i-1]-$a[$i-2]) 
# First condition difference between $a[i] and $a[$i-1] is greater or equal $a[$i-1] and $a[$i-2]
# if $a[$-1] == 1 $a[$i-2] will be interpreted as 0
&&  ## AND Operator with the second condition
(
(($x)%2)==   # See if the difference is even or odd
(($m=(($a[$i]+$x)*$a[$i-1])%$a[$i])%2)&&$m>array_sum($q)
# After that we multiply the result with the lower value *$a[$i-1]
    # for this result we calculate the modulo of the result with the greater value %$a[$i]
    # if the difference and the modulo are both even or odd this belongs to true
# and the modulo of the result must be greater as the sum of these before
    # Ask me not why I have make try and error in an excel sheet till I see this relation
||
(($x)%2)==0&&(($a[$i]-$a[$i-2])*2%$y)==0 # or differce modulator is even and difference $a[$i],$a[$i-1] is a multiple of half difference $a[$i-1],$a[$i-2] 
||
in_array($m,$w) # if the modulo result is equal to the values that we have check till this moment in the array we can also neglect the comparison
)
?:$t=0; # other cases belongs to false
echo$t; #Output

看来我看到的表格包含[1,2,3,4,5,6]中的值,并且我仅更改了最后一项直到9。对于2to3和4to5,我们在模运算

table{width:95%;}th,td{border:1px solid}
<table><tr><th>difference</th><td></td><td>1</td><td>1</td><td>1</td><td>1</td><td>1</td></tr>
<tr><th>difference modulo 2</th><td></td><td>1</td><td>1</td><td>1</td><td>1</td><td>1</td></tr>
<tr><th>value</th><td>1</td><td>2</td><td>3</td><td>4</td><td>5</td><td>6</td></tr>
<tr><th>result</th><td></td><td>3</td><td>8</td><td>15</td><td>24</td><td>35</td></tr>
<tr><th>modulo value great</th><td></td><td>1</td><td>2</td><td>3</td><td>4</td><td>5</td></tr>
<tr><th>modulo 2</th><td></td><td>1</td><td>0</td><td>1</td><td>0</td><td>1</td></tr>
<tr><th></th><td></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><th>difference</th><td></td><td>1</td><td>1</td><td>1</td><td>1</td><td>2</td></tr>
<tr><th>difference modulo 2</th><td></td><td>1</td><td>1</td><td>1</td><td>1</td><td>0</td></tr>
<tr><th>value</th><td>1</td><td>2</td><td>3</td><td>4</td><td>5</td><td>7</td></tr>
<tr><th>result</th><td></td><td>3</td><td>8</td><td>15</td><td>24</td><td>45</td></tr>
<tr><th>modulo value great</th><td></td><td>1</td><td>2</td><td>3</td><td>4</td><td>3</td></tr>
<tr><th>modulo 2</th><td></td><td>1</td><td>0</td><td>1</td><td>0</td><td>1</td></tr>
<tr><th></th><td></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><th>difference</th><td></td><td>1</td><td>1</td><td>1</td><td>1</td><td>3</td></tr>
<tr><th>difference modulo 2</th><td></td><td>1</td><td>1</td><td>1</td><td>1</td><td>1</td></tr>
<tr><th>value</th><td>1</td><td>2</td><td>3</td><td>4</td><td>5</td><td>8</td></tr>
<tr><th>result</th><td></td><td>3</td><td>8</td><td>15</td><td>24</td><td>55</td></tr>
<tr><th>modulo value great</th><td></td><td>1</td><td>2</td><td>3</td><td>4</td><td>7</td></tr>
<tr><th>modulo 2</th><td></td><td>1</td><td>0</td><td>1</td><td>0</td><td>1</td></tr>
<tr><th></th><td></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><th>difference</th><td></td><td>1</td><td>1</td><td>1</td><td>1</td><td>4</td></tr>
<tr><th>difference modulo 2</th><td></td><td>1</td><td>1</td><td>1</td><td>1</td><td>0</td></tr>
<tr><th>value</th><td>1</td><td>2</td><td>3</td><td>4</td><td>5</td><td>9</td></tr>
<tr><th>result</th><td></td><td>3</td><td>8</td><td>15</td><td>24</td><td>65</td></tr>
<tr><th>modulo value great</th><td></td><td>1</td><td>2</td><td>3</td><td>4</td><td>2</td></tr>
<tr><th>modulo 2</th><td></td><td>1</td><td>0</td><td>1</td><td>0</td><td>0</td></tr></table>


为什么你分开的", "时候分开","; 为什么在可以列出清单时分开?为什么在可以排序列表时进行排序?(我仍然不确定您使用的方法是否可靠,有证据吗,因为我浏览的文献似乎暗示它比我认为您的代码更难。)
Jonathan Allan

@JörgHülsermann对不起,如果造成任何混乱,虽然现在有所不同,但您可以选择排序列表。
小麦巫师

恐怕我认为您不仅仅需要对mod 2进行差异方面的测试,还可以举例说明[1,2,5,11,17]。也许看看我的答案中链接的论文。
乔纳森·艾伦

...并且只是用自豪的haskeller的代码而不是我的代码来确认:ideone.com/C022x0
Jonathan Allan

@WheatWizard是[1,2,5,11,17]是还是假?
约尔格Hülsermann

4

的JavaScript(ES6),116 125 130

l=>eval("r=(d,k)=>d?--k&&l.map(v=>v>d||r(d-v,k)):x=1;for(x=l[0]*2;--x>1;r(x,g))g=0,h=x,l.map(v=>(g+=h/v|0,h%=v));x")

这需要输入数组以降序排序。对于从2N到2(N是最大硬币值)的每个值,它会从贪婪算法中找到硬币的数量,然后尝试找到较小的硬币集。

少打高尔夫球

l=>{
  // recursive function to to find a smaller set of coins
  // parameter k is the max coin limit
  r = (d,k) => d // check if difference is not 0
     ? --k // if not, and if the number of coins used will be less than limit
      && l.map(v => v>d || r(d-v, k))  // proceed with the recursive search
     : x=1 // if diff is 0, value found, set x to 1 to stop the loop
  for( x=l[0]*2; --x > 1; )  
    g=0, h=x, l.map(v=>(g += h/v|0, h %= v)), // find g with the greedy algorithm
    r(x,g) // call with initial difference equal to target value
  return x
}

测试

f=
l=>eval("r=(d,k)=>d?--k&&l.map(v=>v>d||r(d-v,k)):x=1;for(x=l[0]*2;--x>1;r(x,g))g=0,h=x,l.map(v=>(g+=h/v|0,h%=v));x")

/* No eval
f=l=>{
  r=(d,k)=>d?--k&&l.map(v=>v>d||r(d-v,k)):x=1;
  for(x=l[0]*2;--x>1;r(x,g))
    g=0,h=x,l.map(v=>(g+=h/v|0,h%=v));
  return x;
}*/

;[
 [[100,50,20,10,5,2,1],1], [[4,3,1],0],
 [[25,10,5,1],1], [[25,10,6,1],0],
 [[3,2,1],1], [[30,17,8,1], 0], 
 [[12,8,3,1],0], [[13,8,2,1], 0]
].forEach(t=>{
  var i=t[0],k=t[1],r=f(i),
      msg=((r==k)?'OK ':'KO ')+i+' -> '+r
      + (r==k?'':' (should be '+k+')')
  O.textContent += msg+'\n'
})

function test()
{
  var i=I.value.match(/\d+/g).map(x=>+x).sort((a,b)=>b-a)
  O.textContent = i+' -> '+f(i)+'\n'+O.textContent
 }
#I { width:50% }
<input id=I value='1 4 9'><button onclick='test()'>test</button>
<pre id=O></pre>


4

Python,218211205字节

-1个字节感谢@TuukkaX(<3和之间可以删除空格or

from itertools import*
g=lambda x,c,n=0:x and g(x-[v for v in c if v<=x][0],c,n+1)or n
lambda c:len(c)<3or 1-any(any(any(x==sum(p)for p in combinations(c*i,i))for i in range(g(x,c)))for x in range(c[0]*2))

代表

以降序输入。

可怕的蛮力。任何一组单个单位硬币和一些其他硬币都是规范的。对于较大的集合,最小的失败情况(如果存在的话)将大于或等于第3个最小的硬币(不确定自己如何相等!)并且小于两个最大的硬币的总和-请参阅本文(实际上是引用另一个,但也给出了O(n ^ 3)方法)。

g 计算贪婪方法使用的硬币,未命名的函数遍历可能的候选对象(实际上是从0到小于最大硬币两倍的硬币以节省字节),并寻找任何数量较少的硬币,这些硬币的总和也等于该数量。

g通过执行收银员的工作原理,它递归地取出最大硬币(小于或等于仍要弥补[v for v in c if v<=x][0]的数量),并计算所用硬币的数量n

如果len(c)小于3,则未命名函数将返回1 ;否则,通过收集每种硬币中的许多硬币,来测试不是这样的情况,1-...即可能性范围内的任何值range(c[0]*2)))都可以用较少的硬币i in range(g(x,c))来实现,c*i,并检查所有i硬币组合combinations(c*i,i),以查看是否有总和等于相同值的硬币。


@WheatWizard对于[13,8,2,1]返回False-我将其添加到测试用例中。进一步说明输入是降序排列的。
乔纳森·艾伦

1
3or应该管用。
Yytsi

感谢@TuukkaX,我也可以替换not(...)1-...
Jonathan Allan

2

果冻(叉子),15 14字节

SRæFµS€Ṃ=$Ṫµ€Ȧ

该解决方案使用的范围从这个反例的纸张。在那里,作者为反例使用了一个严格的界限,但是为了打高尔夫球,使用的硬币面额总和的范围较大,并包含该界限。

该程序可以在不到一秒钟的时间内计算出我机器上的所有测试用例。

不幸的是,这依赖于Jelly的一个分支,我在该分支上实现了Frobenius求解原子,因此您无法在线进行尝试。

用法

$ ./jelly eun 'SRæFµS€Ṃ=$Ṫµ€Ȧ' '1,2,4,6,8'
1

性能良好,可以在不到一秒钟的时间内一次解决所有测试用例。

$ time ./jelly eun 'SRæFµS€Ṃ=$Ṫµ€Ȧ¶Ç€' '[[1,3,4],[1,5,10,25],[1,6,10,25],[1,2,3],[1,8,17,30],[1,3,8,12],[1,2,8,13],[1,2,4,6,8]]'
[0, 1, 0, 1, 0, 0, 0, 1]

real    0m0.793s
user    0m0.748s
sys     0m0.045s

说明

SRæFµS€Ṃ=$Ṫµ€Ȧ  Input: list of integers C
    µ           Start a new monadic chain
S                 Sum
 R                Range, [1, 2, ..., sum(C)]
  æF              Frobenius solve for each X in the range using coefficients from C
                  This generates all vectors where the dot product of a
                  vector with C equals X, ordered by using values from the
                  start to end of C
           µ€   Start a new monadic chain that operates on each list of vectors
     S€           Sum each vector
         $        Monadic hook on the sums
       Ṃ            Minimum (This is the optimal solution)
        =           Vectorized equals, 1 if true else 0
          Ṫ       Tail (This is at the index of the greedy solution)
             Ȧ  All, returns 0 if it contains a falsey value, else 1

2

的JavaScript(ES6),144个 132 124 122 110字节

a=>![...Array(a[0]*2)].some((_,i)=>(g=(a,l=0,n=i)=>[a.filter(c=>c>n||(l+=n/c|0,n%=c,0)),-l*!n])(...g(a))[1]>0)

要求数组以降序排序。使用链接的论文中的观察结果,如果系统不是规范的,则至少存在一个小于2a [0]的值,当使用初始贪婪算法中的未使用硬币进行分解时,它将花费较少的硬币。

编辑:通过意识到即使我已经达到目标值也可以检查所有硬币,节省了12个字节。保存的8个字节通过从切换我的中间输出[l,b][b,-l]; 这使我可以将第一个结果直接传递为第二个调用的参数,另外还可以节省一点钱来检测第二个调用是否成功。通过将的定义移动gsome回调中,节省了2个字节,这使我避免了不必要的两次传入循环变量。从我的递归辅助函数切换到调用可以节省12个字节filter(由我的中间输出开关实现)。


2

Perl,69个字节

包括+2 -pa

在STDIN上按降序排列硬币。您可以选择省略1硬币。

coins.pl <<< "4 3 1"

coins.pl

#!/usr/bin/perl -pa
$_=!map{grep$`>=$_&&($n=$G[$`-$_]+1)<($G[$`]||=$n),@F,/$/}1..2*"@F"

建立收银员算法使用的硬币数量,@G数量为最大硬币的1到两倍。对于每个金额,请检查是否将该金额减少1个硬币值,出纳员算法最多需要减少1个硬币。如果不是,则这是一个反例(或者以前有一个反例)。我可以在第一个反例中停下来,但这需要更多字节。所以时间复杂O(max_coin * coins)度是O(max_coin)

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.