尤拉計劃696:麻將

lt發表於2020-01-05

字數太多,上圖 Mahjong

麻將的遊戲是玩s種花色的牌.每張牌也有一個在範圍1..n內的數字,對於每個花色/數字組合,正好有四張相同花色和數字的牌。(真正的麻將遊戲也包含其他獎賞牌,但這些在此問題中不考慮。)

糊牌的一手是3t+2張牌(其中t是固定整數),它可以安排為t個 三重和一對,其中:

  • 三重是順子或碰
  • 順子是三張相同花色和連續數字的牌
  • 碰是三張相同的牌(相同花色和相同數字)
  • 對是兩張相同的牌(相同花色和相同數字)

例如,下面是一隻勝利的手,它包括兩個順子,兩個碰和一對:

請注意,有時相同的切片集合可以以多種方式表示為三重和一對。這隻能算作一隻糊牌的手。例如,下面被認為是與上面相同的糊牌手,因為它們由相同的牌組成:

設 w(n,s,t)是t個三重和一對形成不同的糊牌手的數量,其中牌的花色數為s,數字最大為n。

例如,對於單個花色和牌數字最大為 4,我們有w(4,1,1)=20 :其中有12只糊牌的手,包括一個碰和一對,另外8個糊牌的手,包括一隻順子和一對。已知

w(9,1,4)=13259 w(9,3,4)=5237550 w(1000,1000,5)≡107662178(mod 1000000007)

求:w(10^8 ,10^8 ,30)給出mod 1000000007的答案.

用sql模擬w(4,1,1)

with p as
(select level||','||level||',' n, power(10,level)*2 flg from dual connect by level<=9)
,c as
(select level||','||level||','||level||',' n , power(10,level)*3 flg from dual connect by level<=9
union
select level||','||(level+1)||','||(level+2)||',' n, power(10,level)*111 flg from dual connect by level<=9-2)
,t(lv,s,flg) as(
select 0,n s,flg from p
union all
select lv+1,s||n,t.flg+c.flg from t,c where lv<4 and regexp_instr(t.flg+c.flg,'[5-9]')=0)
select count(*),count(distinct flg) from t where lv=4 ;

COUNT(*) COUNT(DISTINCTFLG)
-------- ------------------
  300366              13259

用相同思路實現的python遞迴程式如下:

import re
def pai(n):
 c=[[i,i] for i in range(1,n+1)]
 return c

def peng(n):
 c=[[i,i,i] for i in range(1,n+1)]
 return c

def chow(n):
 c=[[i,i+1,i+2] for i in range(1,n-1)]
 return c


x=chow(9)+peng(9)
x.sort()
lenx=len(x)
xflg=[0]*lenx
for i in range(0,lenx):
 for j in x[i]:
  xflg[i]+=10**j


print(xflg) 
cnt=0

def mj(lv,flg,last_i):
 global cnt
 global set_a
 if last_i==lenx: return -1
 if lv==4 : cnt+=1;set_a.add(flg);return 0 
 for i in range(last_i,lenx):
  newflg= xflg[i]+flg
  m=re.search('[5-9]',str(newflg))
  if m==None: #each number occurs less than 5 times
   mj(lv+1,newflg,i)

set_a=set()
cnt=0
for i in pai(9):
 mj(0,(10**i[0])*2,0)


print(cnt) 
print(len(set_a))

def wt():
 fileObject = open('sampleList.txt', 'w')
 for ip in set_a:
     fileObject.write(str(ip))
     fileObject.write('\n')
 fileObject.close()
輸出結果
>>> print(cnt)
14738
>>> print(len(set_a))
13259

修改上述程式碼中的引數,得到w(9,3,4)

x=chow(29)+peng(29)
x=[i for i in x if 10 not in i and 20 not in i]
x.sort()

set_a=set()
cnt=0
pa=[i for i in pai(29) if 10 not in i and 20 not in i]
for i in pa:
 mj(0,(10**i[0])*2,0)

>>>> print(cnt)
5296275
>>>> print(len(set_a))
5237550 

重寫了mj2函式和計算總數的w函式

def mj2(lv,flg,last_i,s,t):
 global cnt,lenx
 #print(lenx)
 if last_i==lenx: return -1
 if lv==t : cnt+=1;set_a.add(flg);return 0 
 for i in range(last_i,lenx):
  newflg= xflg[i]+flg
  m=re.search('[5-9]',str(newflg))
  if m==None: 
   mj2(lv+1,newflg,i,s,t)

def w(n,s,t): 
 global x,xflg,pa,lenx,cnt
 x=[]
 for i in chow((n+1)*s-1)+peng((n+1)*s-1):
  for j in range(1,s+1):
   if  (n+1)*j  in i:break 
  else: x.append(i)
 x.sort()
 lenx=len(x)
 xflg=[0]*lenx
 for i in range(0,lenx):
  for j in x[i]:
   xflg[i]+=10**j
 cnt=0
 pa=[]
 for i in pai((n+1)*s-1):
  for j in range(1,s+1):
   if  (n+1)*j  in i:break 
  else: pa.append(i)
 for i in pa:
  mj2(0,(10**i[0])*2,0,s,t)
 return len(set_a)  

for i in range(0,5):set_a=set();x=[];xflag=[];pa=[];lenx=0;cnt=0;print(i+4,w(4+i,1,1))  
4 20
5 35
6 54
7 77
8 104

for i in range(0,5):set_a=set();x=[];xflag=[];pa=[];lenx=0;cnt=0;print(i+4,w(4+i,4,1))
4 368
5 620
6 936
7 1316
8 1760 

總結出w(n,s,1)=ns(2ns-2s-1)

測試w(n,s,2),計算差分,應該是3次方程,用4組值,mathematica解方程組得出w(n,s,2)=s(2s^2n^3-2s(2s+1)n^2+(s+1)(2s-1)n+6)

for i in range(0,20):set_a=set();x=[];xflag=[];pa=[];lenx=0;cnt=0;print(i+4,w(4+i,5,2))
4 8310
5 18880
6 35850
7 60720
8 94990
In[17]:= Solve[
     a*4^3+b*4^2+c*4+d==8310  &&
     a*5^3+b*5^2+c*5+d==18880 &&
     a*6^3+b*6^2+c*6+d==35850 &&
     a*7^3+b*7^2+c*7+d==60720,
     {a,b,c,d}]

Out[17]= {{a -> 250, b -> -550, c -> 270, d -> 30}

t=3的公式

In[115]:= Factor[w53[x]]
                                2         3        4
          5 (-192 - 6 x + 1525 x  - 1650 x  + 500 x )
Out[115]= -------------------------------------------
                               3
In[104]:= Factor[w43[x]]
                                 2        3        4
          4 (-156 + 141 x + 764 x  - 864 x  + 256 x )
Out[104]= -------------------------------------------
                               3
In[93]:= Factor[w33[x]]
                              2        3       4
Out[93]= 3 (-40 + 64 x + 101 x  - 126 x  + 36 x )
In[94]:= Factor[w23[x]]
                              2        3       4
         2 (-84 + 171 x + 70 x  - 120 x  + 32 x )
Out[94]= ----------------------------------------
                            3
In[95]:= Factor[w13[x]]
                          2       3      4
         -48 + 102 x - 7 x  - 18 x  + 4 x
Out[95]= ---------------------------------
                         3

容易觀察到x^4的係數是4 s^3, 常數項的係數是-36 s -12,所以只要求x、x^2、x^3的係數,設x^3的係數是 a s^3+b s^2 +c s +d, 利用上述公式的s=1~4項聯立方程組:

Solve[
a 1^3+b 1^2 +c 1 +d ==-18  &&
a 2^3+b 2^2 +c 2 +d ==-120 &&
a 3^3+b 3^2 +c 3 +d ==-126*3 &&
a 4^3+b 4^2 +c 4 +d ==-864  ,
{a,b,c,d}]

解得{{a -> -12, b -> -6, c -> 0, d -> 0}},所以係數是-12 s^2 -6 s,用s=5代入值為-1650,與w53的表示式比較,結果正確。同樣方法求得x^2的係數12 s^3 +6 s^2 -25 s,x的係數-4 s^3 +97 s +9,所以得到wns3的通項公式為wns3[n_,s_]:=(-36 s -12 +(-4 s^3 +97 s +9)n +(12 s^3 +6 s^2 -25 s)n^2+ (-12 s^3-6 s^2 )n^3+ 4 s^3 n^4)s /3

當t=4時,和t=3的情況類似,前幾個數值無法用公式描述,要從w(9,1,4)開始符合公式

wn14[n_]:=2/3 n^5-4 n^4-26/3 n^3+96 n^2-140 n-61

利用相同辦法,求得:

wn24[n_]:=64/3 n^5-320/3 n^4+128/3 n^3+1712/3 n^2-822 n-46 

在膝上型電腦上執行程式算w(10,3,4)時,產生記憶體不足錯誤。考慮到列表的儲存空間是集合的1/4(參考https://www.ituring.com.cn/article/509014),並且重複數佔總數的比例很低,而計算不重複數只需要最後一步算,因此改寫mj2函式為 而在w函式最後加上排序和不重複計數程式碼。

def mj4(lv,flg,last_i,s,t):
 global cnt,lenx
 #print(lenx)
 if last_i==lenx: return -1
 if lv==t : 
  cnt+=1
  sortlst_a.append(flg)
  return 0
 for i in range(last_i,lenx):
  newflg= xflg[i]+flg
  m=re.search('[5-9]',str(newflg))
  if m==None:
   mj4(lv+1,newflg,i,s,t)


def w3(n,s,t):
 global x,xflg,pa,lenx,cnt
 x=[]
 for i in chow((n+1)*s-1)+peng((n+1)*s-1):
  for j in range(1,s+1):
   if  (n+1)*j  in i:break
  else: x.append(i)
 x.sort()
 lenx=len(x)
 xflg=[0]*lenx
 for i in range(0,lenx):
  for j in x[i]:
   xflg[i]+=10**j
 cnt=0
 pa=[]
 for i in pai((n+1)*s-1):
  for j in range(1,s+1):
   if  (n+1)*j  in i:break
  else: pa.append(i)
 for i in pa:
  mj4(0,(10**i[0])*2,0,s,t)
 slen= len(sortlst_a)
 sortlst_a.sort()
 ucnt=0
 oldv=0
 for i in range(0,slen):
  if sortlst_a[i] !=oldv: ucnt+=1;oldv=sortlst_a[i]
 return ucnt

這樣改造後,利用程式最多可以算到w(12,3,4),離計算5次多項式方程組所需的等式還差2個。
好在根據wnst在t=1,2時的結果,通項公式的最高次係數有規律,2/3,64/3,總結為2*s^t/3,所以最高項次數為2*3^5/3=162(實際上n、s的次數應該相同)。這樣減少一個未知數,只需要額外的1個等式。
再次根據上述經驗,wn34的實際值在n=9前比公式值略小,小的這個值一定是3的倍數,所以嘗試在w(8,3,4)的實際值上加3,作為額外的等式,得到如下方程組:

In[15]:= Solve[
         162*8 ^5+x*8 ^4+a*8 ^3+b*8 ^2+c*8 +d==2661693 &&
         162*9 ^5+x*9 ^4+a*9 ^3+b*9 ^2+c*9 +d==5237550 &&
         162*10^5+x*10^4+a*10^3+b*10^2+c*10+d==9496287 &&
         162*11^5+x*11^4+a*11^3+b*11^2+c*11+d==16149624&&
         162*12^5+x*12^4+a*12^3+b*12^2+c*12+d==26085537,
         {x,a,b,c,d}]

Out[15]= {{x -> -756, a -> 738, b -> 1416, c -> -2343, d -> 117}}

得出

wn34[n_]:=162 n^5-756 n^4+738 n^3+1416 n^2-2343 n+117

相關文章