Let p(n) represent the number of different ways in which n coins can be separated into piles. For example, five coins can separated into piles in exactly seven different ways, so p(5)=7.
OOOOO
OOOO O
OOO OO
OOO O O
OO OO O
OO O O O
O O O O O
Find the least value of n for which p(n) is divisible by one million.
记p(n)是将n枚硬币分拆成堆的不同方式数。例如,五枚硬币有7种分拆成堆的不同方式,因此p(5)=7。
OOOOO
OOOO O
OOO OO
OOO O O
OO OO O
OO O O O
O O O O O
找出使p(n)能被一百万整除的最小n值。
思路:
求数的拆分有多少种
再判断是否能被一百万整除
参考资料:wiki ,PartitionFunctionP
法一:
根据这个等式:
高能预警:
1. 这里是两部分的和
2.当第一个不满足条件,即:n<k(3k-1)/2 时候,第二个一定不成立
3.第一个满足条件,第二个可能不满足条件,这里说的条件都是数组下标不能越界
4.满足条件的都要计算,只有当第一个不满足条件的时候才本次循环
5.前面的(-1)^(k+1),要乘进去,展开计算,就是计算符合条件的数组
关键程序:
for(k=1;k<=n;k++){
gk1 = k*(3*k-1)/2;
gk2 = gk1+k;
if(gk1>n) break;
plist.set(n,plist.get(n)+flag*plist.get(n-gk1));
if(gk2<=n){
plist.set(n,plist.get(n)+flag*plist.get(n-gk2));
}
plist.set(n,plist.get(n)%limit);
flag*=-1;
}
这里由于我只是在上面看到的求解表达式,造成我搞了好久都没有搞出来,没文化正可怕
法二:
看到这里还没有出问题
看到这里,直接根据上面的表达式求解了,然而这里的k不是从1-n,这里我又理解错了,以为拿来用就好了
上面的方法不行,下面的方法也不行,真是浪费了好多时间的
下面程序中有一个求k的过程,这里才是真谛啊!!!
关键程序:
while(gk<=n){
flag = (i%4>1)?-1:1;
plist.set(n,plist.get(n)+flag*plist.get(n-gk));
plist.set(n,plist.get(n)%limit);
i++;
int k= (i%2==0)?i/2+1:-(i/2+1);
gk = k*(3*k-1)/2;
}
Java程序:
package Level3; import java.util.ArrayList; public class PE078{ void run(){
int limit = 1000000;
partitions2(limit);
}
void partitions2(int limit){
ArrayList<Integer> plist = new ArrayList<Integer>();
plist.add(1);
int n = 1;
while(true){
int gk1 =1;
int gk2 =2;
int k=1;
plist.add(0);// 初始第n
int flag = 1;
for(k=1;k<=n;k++){
gk1 = k*(3*k-1)/2;
gk2 = gk1+k;
if(gk1>n) break;
plist.set(n,plist.get(n)+flag*plist.get(n-gk1));
if(gk2<=n){
plist.set(n,plist.get(n)+flag*plist.get(n-gk2));
}
plist.set(n,plist.get(n)%limit);
flag*=-1;
}
if(plist.get(n)==0)
break;
n++;
}
System.out.println(n);
}
// 55374
// running time=0s784ms
void partitions1(int limit){
ArrayList<Integer> plist = new ArrayList<Integer>();
plist.add(1);
int n = 1;
int flag;
while(true){
int gk = 1;
int i = 0;
plist.add(0);
while(gk<=n){
flag = (i%4>1)?-1:1;
plist.set(n,plist.get(n)+flag*plist.get(n-gk));
plist.set(n,plist.get(n)%limit);
i++;
int k= (i%2==0)?i/2+1:-(i/2+1);
gk = k*(3*k-1)/2;
} if(plist.get(n)==0)
break;
n++;
}
System.out.println(n);
}
// 55374
// running time=1s155ms public static void main(String[] args){
long t0 = System.currentTimeMillis();
new PE078().run();
long t1 = System.currentTimeMillis();
long t = t1 - t0;
System.out.println("running time="+t/1000+"s"+t%1000+"ms"); }
}
法三:
又给出了求k的一种方式
关键程序:
while True:
gk = k * (3 * k - 1) // 2
i = n - gk
if i < 0:
break
pt += (-1) ** (k * k + 1) * p[i]
k = -1 * k if k > 0 else 1 - k
p.append(pt)
python程序:
import time ; def partitions(limit):
p = [1, 1, 2]
n = 2
while True:
n += 1
pt = 0
i = 0
k = 1
while True:
gk = k * (3 * k - 1) // 2
i = n - gk
if i < 0:
break
pt += (-1) ** (k * k + 1) * p[i]
k = -1 * k if k > 0 else 1 - k
p.append(pt)
if pt % limit == 0:
print "n =", n, "\n"+"partition =", pt
break if __name__=='__main__':
t0 = time.time()
limit = 1000000
partitions(limit)
t1 = time.time()
print "running time=",(t1-t0),"s" # n = 55374
# running time= 21.3049998283 s
说明:只有第一种方法是我自己写的,其他是在论坛看到的,自己整理的