n个不同的球放入r个不同的盒子中,可以先把r个盒子视为相同的,即先求出n个不同的球放入r个相同的盒子中,且不允许有空盒子的不同方案数,设为S(n,r);再将r个盒子进行全排列,共有r!
。
记号f(n,r)为n个不同的球放入r个不同盒子中,且不允许有空盒子的不同放法,根据乘法原理,有f(n,r)=S(n,r)·r!
如何求S(n,r)?
请看如下定义:
定义n个有区别的球放入r个相同的盒子中,要求无一空盒,其不同的方案数s(n,r)称为第二类Stirling数。
S(n,r)=r·S(n-1,r)+S(n-1,r-1)(n>1,r≥1)
证明:
设n个不同的球为b1,b2,…,bn,从中取一个球设为b1。
把这n个球放入r个盒子无一空盒的方案全体可分为两类:
(1)b1独占一盒,其余n-1个球放入r-1个盒子无一空盒的方案数为S(n-1,r-1)
(2)b1不独占一盒,这相当于先将b2,b3,…,bn。
这n-1个球放入r个盒子,不允许有空盒的方案数共有S(n-1,r),然后将b1放入其中一盒,这一盒子有r种可挑选,故b1不独占一盒的方案数为r·S(n-1,r)。
根据加法原理,则有:
S(n,r)=r·S(n-1,r)+S(n-1,r-1)(n>1,r≥1)
对于n=r,或r=1,显然有S(n,n)=1,S(n,1)=1。
综上所述,本题的算法可用第二类Stirling数的递推关系先求出S(n,r),再乘上r!
,即得所求方案数。
【参考程序】
programfgduif;
varn,r,j,i:
longint;
s:
array[0..20,0..20]oflongint;
begin
assign(input,'box.in');
reset(input);
assign(output,'box.out');
rewrite(output);
fillchar(s,sizeof(s),0);
readln(n,r);
fori:
=1tondo
forj:
=1tordo
ifi=jthens[i,j]:
=1
elseifj=1thens[i,j]:
=1
elses[i,j]:
=j*s[i-1,j]+s[i-1,j-1];//求第二类Stirling数
fori:
=1tordo
s[n,r]:
=s[n,r]*i;
writeln(s[n,r]);
close(input);
close(output);
end.
9.4
取数游戏
源程序名cycle.?
?
?
(pas,c,cpp)
可执行文件名cycle.exe
输入文件名cycle.in
输出文件名cycle.out
【问题描述】
有一个取数的游戏。
初始时,给出一个环,环上的每条边上都有一个非负整数。
这些整数中至少有一个0。
然后,将一枚硬币放在环上的一个节点上。
两个玩家就是以这个放硬币的节点为起点开始这个游戏,两人轮流取数,取数的规则如下:
(1)选择硬币左边或者右边的一条边,并且边上的数非0;
(2)将这条边上的数减至任意一个非负整数(至少要有所减小);
(3)将硬币移至边的另一端。
如果轮到一个玩家走,这时硬币左右两边的边上的数值都是0,那么这个玩家就输了。
如下图,描述的是Alice和Bob两人的对弈过程,其中黑色节点表示硬币所在节点。
结果图(d)中,轮到Bob走时,硬币两边的边上都是0,所以Alcie获胜。
2000
○----------○○-----------○○----------○○---------○
||||||||
0||50||50||10||0
||||||||
○--------------○○----------○○----------○○---------○
3333
(a)Alice(b)Bob(c)Alice(d)Bob
现在,你的任务就是根据给出的环、边上的数值以及起点(硬币所在位置),判断先走方是否有必胜的策略。
【输入】
第一行一个整数N(N≤20),表示环上的节点数。
第二行N个数,数值不超过30,依次表示N条边上的数值。
硬币的起始位置在第一条边与最后一条边之间的节点上。
【输出】
仅一行。
若存在必胜策略,则输出“YES”,否则输出“NO”。
【样例】
cycle.incycle.out
4YES
2530
cycle.incycle.out
3NO
000
【问题分析】
考虑一个简化的问题。
如果硬币的一边的数值为0,那么惟一可能取胜的走法就是向另一边移动,并且把边上的数减到0。
因为如果不把边上的数减到0,那么下一步对方会将硬币移动到原来的位置,并且将边上的数减到0,这样硬币两边的数值就都为0了。
所以,对于一边有0的情况,双方惟一的走法就是不停的向另一边移动,并取完边上的数值。
因此,判断是否有必胜策略,就是看另一个方向上连续的非零边是否为奇数条。
那么两边都非零的情况呢?
如果有一个方向上连续的非零边为奇数条,那么显然是有必胜策略的,因为只需往这个方向走并取完边上的数即可。
如果两个方向上连续的非零边都是偶数条,则没有必胜策略。
因为不管往哪个方向走,必然不能取完边上的数,否则必败。
如果不取完,则下一步对方可以将硬币移动回原来的位置并取完边上的数,这样就变成了一边为0、另一边有偶数条连续的非零边的情况,还是必败。
所以,对于一般的情况,只需判断硬币的两边是否至少有一边存在奇数条连续的非零边。
如果存在,则有必胜策略;否则无必胜策略。
算法的时间复杂度为O(N)。
【参考程序】
programcgfdj;
vari,n,left,right:
longint;//left表示左边有几个连续非零数,right表示右边有几个连续非零数
a:
array[1..20]oflongint;
begin
assign(input,'cycle.in');
reset(input);
assign(input,'cycle.out');
rewrite(output);
readln(n);
fori:
=1tondo
read(a[i]);
left:
=0;
right:
=0;
fori:
=1tondo
ifa[i]<>0theninc(right)//统计右边
elsebreak;
fori:
=ndownto1do
ifa[i]<>0theninc(left)//统计左边
elsebreak;
ifodd(left)orodd(right)thenwriteln('YES')//有必胜策略
elseif(notodd(left))and(notodd(right))thenwriteln('NO');//无必胜策略
close(input);
close(output);
end.
9.5磁盘碎片整理
源程序名defrag.?
?
?
(pas,c,cpp)
可执行文件名defrag.exe
输入文件名defrag.in
输出文件名defrag.out
【问题描述】
出于最高安全性考虑,司令部采用了特殊的安全操作系统,该系统采用一个特殊的文件系统。
在这个文件系统中所有磁盘空间都被分成了相同尺寸的N块,用整数1到N标识。
每个文件占用磁盘上任意区域的一块或多块存储区,未被文件占用的存储块被认为是可是用的。
如果文件存储在磁盘上自然连续的存储块中,则能被以最快的速度读出。
因为磁盘是匀速转动的,所以存取上面不同的存储块需要的时间也不同。
读取磁盘开头处的存储块比读取磁盘尾处的存储块快。
根据以上现象,我们事先将文件按其存取频率的大小用整数1到K标识。
按文件在磁盘上的最佳存储方法,1号文件将占用1,2,…,S1的存储块,2号文件将占用S1+1,S1+2,…,S1+S2的存储块,以此类推(Si是被第i个文件占用的存储块的个数)。
为了将文件以最佳形式存储在磁盘上,需要执行存储块移动操作。
一个存储块移动操作包括从磁盘上读取一个被占用的存储块至内存并将它写入其他空的存储块,然后宣称前一个存储块被释放,后一个存储块被占用。
本程序的目的是通过执行最少次数的存储块移动操作,将文件安最佳方式存储到磁盘上,注意同一个文件的存储块在移动之后其相对次序不可改变。
【输入】
每个磁盘说明的第一行包含两个用空格隔开的整数N和K(1≤K≤N≤100000),接下来的K行每行说明一个文件,对第i个文件的说明是这样的:
首先以整数Si开头,表示第i个文件的存储块数量,1<=Si<=N-K,然后跟Si个整数,每个整数之间用空格隔开,表示该文件按自然顺序在磁盘上占用的存储块的标识。
所有这些数都介于1和N之间,包括1和N。
一个磁盘说明中所有存储块的标识都是不同的,并且该盘至少有一个空的存储块。
【输出】
对于每一个磁盘说明,只需输出一行句子“WeneedMmoveoperations”,M表示将文件按最佳方式存储到磁盘上所需进行的最少存储块移动操作次数。
如果文件已按最佳方式存储,仅需输出“Nooptimizationneeded.”。
【样例】
defrag.indefrag.out
203Weneed9moveoperations
4231112
17
318510
【问题分析】
本题可以看作是一个置换的调整问题。
按文件块的目标位置对文件块编号后,调整文件块位置的问题就转化为调整置换的问题了。
不过,本题还有一点特殊之处,就是存在一些数,它们不要求归位,这些数可作为交换空间。
找出所有的循环节,分情况讨论。
如果是一条链,则只需移动链的长度次,若是一个环则需要移动环的长度+1次
【参考程序】
programfgjk;
varn,k,s,i,j,m,t:
longint;
a:
array[1..100000]oflongint;
v:
array[1..100000]ofboolean;
begin
assign(input,'defrag.in');
reset(input);
assign(output,'defrag.out');
rewrite(output);
fillchar(a,sizeof(a),0);
fillchar(v,sizeof(v),false);
readln(n,k);
m:
=0;
fori:
=1tokdo
begin
read(s);
forj:
=1tosdo
begin
read(t);
inc(m);
a[m]:
=t;
ifm=tthenv[t]:
=true;//已经处于原来位置
end;
end;
s:
=0;
fori:
=1tomdo
ifnotv[a[i]]then//判断是否需要移动
begin
k:
=a[i];
j:
=a[i];//记住初始位置
t:
=0;
repeat//寻找链或环
v[k]:
=true;
inc(t);
k:
=a[k];
until(k=0)or(v[k]);//得到一条链或一个环
ifk=0theninc(s,t)//得到的是链
elseifk=jtheninc(s,t+1)//得到的为环
elseinc(s,t);//得到的是链
end;
ifs>0thenwriteln('Weneed',s,'moveoperations.')
elsewriteln('Nooptimizationneeded.');
close(input);
close(output);
end.
9.6欧几里德的游戏
源程序名game.?
?
?
(pas,c,cpp)
可执行文件名game.exe
输入文件名game.in
输出文件名game.out
【问题描述】
欧几里德的两个后代Stan和Ollie正在玩一种数字游戏,这个游戏是他们的祖先欧几里德发明的。
给定两个正整数M和N,从Stan开始,从其中较大的一个数,减去较小的数的正整数倍,当然,得到的数不能小于0。
然后是Ollie,对刚才得到的数,和M,N中较小的那个数,再进行同样的操作……直到一个人得到了0,他就取得了胜利。
下面是他们用(25,7)两个数游戏的过程:
Start:
257
Stan:
117
Ollie:
47
Stan:
43
Ollie:
13
Stan:
10
Stan赢得了游戏的胜利。
现在,假设他们完美地操作,谁会取得胜利呢?
【输入】
第一行为测试数据的组数C。
下面有C行,每行为一组数据,包含两个正整数M,N。
(M,N不超过长整型。
)
【输出】
对每组输入数据输出一行,如果Stan胜利,则输出“Stanwins”;否则输出“Olliewins”
【样例】
game.ingame.out
2Stanwins
257Olliewins
2415
【问题分析】
这是一道博弈题。
解题的关键在于把握胜负状态的关系。
任意的状态只有两种可能:
一种可能是胜状态——即有必胜策略,另一种可能是负状态——即没有必胜策略。
对于任意的胜状态,无论如何走,都不可能走到另一个胜状态;而任意的负状态,至少存在一种走法可以走到胜状态。
(0,0)是初始的胜状态。
考察任意的状态(a,b),不妨假设a≥b。
如果a/b<2,则只有一种走法,即将(a,b)变为(a-b,b)。
那么,(a,b)是何种状态就取决于(a-b,b)是何种状态:
根据前面胜负状态的定义可知,(a-b,b)为胜状态时,(a,b)为负状态;(a-b,b)为负状态时,(a,b)为胜状态。
如果a/b≥2,则至少存在两种走法:
将(a,b)变为(c,b)或(c+b,b),这里c=amodb。
如果这两个状态中至少有一个是负状态,则根据定义(a,b)是胜状态。
如果两个状态都是胜状态,由于(c+b,b)可以变为(c,b),这就与“胜状态只能走到负状态”产生了矛盾,所以两个状态都是胜状态的情况是不存在的。
因此,a/b≥2时,(a,b)必为胜状态。
总结一下前面的