NOIP提高组解题报告c语言Word格式.docx
《NOIP提高组解题报告c语言Word格式.docx》由会员分享,可在线阅读,更多相关《NOIP提高组解题报告c语言Word格式.docx(104页珍藏版)》请在冰豆网上搜索。
{节省空间,因为递推的时候只与上个阶段有关,故只保留相邻两个阶段}
高精度乘法的方法我不是用的模拟手算的过程(这个大家会做吧),而用了类似
多项式乘法的方法,因为我觉得这种写法很好记!
(大家仔细看看我的mul过程)
第三题:
搜索。
数据很小,因此回溯就可以了。
程序先预处理,建立矩阵add[i,j]
即第j个串连在第i个串之后能增加的长度。
0代表j不能增加i的后面。
一个需要注意的地方:
计算add[i,j]的时候要计算最大可能值!
例如:
ABABABAB和ABABABC就是5,不是1!
现在没有问题了吧。
为了方便和直观,我采用递归实现回溯搜索。
第四题:
有同学搜索第一个人,拣了以后第二个人用动态规划,一定能得最优解,但时间效率不大高。
有同学采用贪心,即用动态规划算出第一个人最大能拣的数,再在剩下的数中用动态规划。
反例如下:
191
000
第一次是:
1->
9->
1
第二次是:
和是21
但显然可以两次把所有的数拣完(22)。
本题是典型的多维动态规划,很象IOI93的第四题,另一个算法是网络流,很象IOI97第一题,
这里我只分析前者。
这道题目的简单之处是阶段很好划分(对角线),这种方法我就不介绍了,
因为很多地方都有介绍。
这里讲一种怪一点的动态规划^_^
容易想到的思路是:
令d[x1,y1,x2,y2]表示甲在x1,y1,乙在x2,y2以后(包括取走(x1,y1))的过程中可以获得的最大和。
则d[1,1,1,1]就是原问题的解。
但是是否能取数和另一个人是否事先已经到过该格子有关,我们需要设计一种走的方法,使得只根据x1,y1,
x2,y2就能判断一些关键的格子是否已经到达过。
这样,问题才具有无后效性。
为此,我们把路线作如下处理:
1)当两个人路线有交叉的时候,改成等效的,不交叉的。
如下图,1代表第一个人,2代表第二个人。
X代表相遇点。
X111
21
222X2
12
1X1
2X
变成:
X222
12
111X2
1X2
1X
反正让2走右边就行了。
2)现在1在第y1列,2在第y2列,让1和2分别向右走,到达yy1和yy2列,然后向下走一格
这样如果yy1<
y2,便是分别取走第y1~yy1,y2~yy2列数,否则路线有重复,就取走y1~yy2的数。
为了方便连续取数,我用了一个sum[x,y1,y2]的数组,就是第x行的y1~y2的数。
请看我的程序中的相应部分。
这样,所有的走法都可以转换成上述的,具有无后效性的结构了。
由于递推是从d[x1,y1,x2,y2]到d[x1+1,y1'
x2+1,y2'
],而总有x1=x2=x,所以可以把状态节省为:
d[y1,y2]
而把x(当前行)作为阶段来递推:
forx:
=n-1downto1do
fory1:
fory2:
=y1tondo
枚举y1'
和y2'
作为新的y1和y2,注意保证y1'
>
=y1,y2'
=y2(题目规定),y1'
<
=y2'
(刚才的分析),递推d[y1,y2]
{只记录相邻两个状态}
边界是什么呢?
当然是从第n列的值了,就是sum[n,y1,n](从y1取到n)
说了这么多,真不知道我说清楚了没有(好象是没有:
-P),如果有什么不明确的地方,请大家在论坛上提出来。
一句话,就是两个人一起,一行一行往下走,每一行可以一次向右走若干步,但是要保证2在1的右边。
注意最后输出的是d2[1,1]。
如果输出d1[1,1],n=1会得到0。
(为什么?
自己想啊)
现在程序就不难写了吧。
程序见附件:
第七届(2001)分区联赛复赛解题报告(提高组)
俞玮赵爽
一元三次方程求解
给出一个三次方程,试求它所有的三个根。
这里所有的根都在区间中,并保证方程具有三个实根,且它们之间的差不小于1。
分析:
如果是一般的求三次方程根的问题,那么只能直接使用求根公式,但这是非常复杂的。
由于题目要求只精确到0.01,故我们考虑一下是否可以应用数值方法进行计算。
由题目给出的方程在区间内存在根的条件可知,我们可以用一个变量从-100.000到100.000以步长0.001做循环。
若,则可知在区间内存在方程的解。
这样无论这个区间内的解是什么,在取两位小数的精度后都与取两位小数的结果是一样的。
故我们就可以把取两位小数精度的作为问题的解。
另外还有可能方程的根在区间端点的情况,我们可以通过判断是否为0来获得。
但这种方法由于用到了题目所要求的数值精度小的特殊性,故无法扩展。
而在用数值方法求方程根的时候,我们最常用的方法就是二分法。
该方法的应用在于如果确定了方程在区间内如果存在且只存在一个根,那么我们可以用逐次缩小根可能存在范围的方法确定出根的某精度的数值。
该方法主要利用的就是题目所给的若在区间内存在一个方程的根,则这个事实,并重复执行如下的过程:
l取当前可能存在解的区间;
l若或,则可确定根为并退出过程;
l若,则由题目给出的定理可知根在区间中,故对区间重复该过程;
l若,则必然有,也就是说根在中,应该对此区间重复该过程。
最后,就可以得到精确到0.001的根。
再考虑什么样的区间内会有根。
由于题目给出了所有的根都在-100到100之间,且根与根之间差不小于1的限制条件,故我们可知,在、、……、、这201个区间内,每个区间内至多只能存在一个根。
这样对除去区间外的其他的区间,要么,要么时这个方程在此区间内才有解。
若,显然为解;
若,则可以利用上面所说的方法球出解来。
这样就可求出这个方程的所有解。
最后是输出的解要求排序。
我们既可以在求出来之后排序,也可以从小到大的顺序求解,就可以按照升序求出所有解。
数据:
输入输出
11-2-12-1.001.002.00
21-4.652.251.4-0.351.004.00
3110-1-10-10.00-1.001.00
41-1.8-8.59-0.84-2.10-0.104.00
数的划分
求把一个整数无序划分成份互不相同的正整数之和的方法总数。
这是一道整数剖分的问题。
这类问题的数学性很强,方法也很多。
这里介绍一种较为巧妙的办法。
我们不必拘泥于原问题如何求解,而把思维转换一个角度来考虑一个与原问题等价的问题。
我们可以形象的把n的k-自然数剖分看作把n块积木堆成k列,且每列的积木个数依次递增,也就是这n块积木被从左到右积木被堆成了“楼梯状”。
比如,下图是10的几种4-剖分。
现在,我们不妨把这三个图顺时针旋转90度,成为:
不难发现,旋转之后的模型还是10的剖分,不过约束条件有所不同。
很明显,由于原来是k剖分,因此新的模型中最大的一个元素必然是k。
而其余的元素大小不限,但都不能大于k。
因此问题转化成了求n的任意无序剖分,其中最大的元素是k。
当然,我们可以把n减去k,成为,剩下的问题就是求的任意剖分,且其中每个元素都不大于k的方案总数了。
求解这个新的模型可以用递推的方法。
用表示把b做任意剖分,其中最大的一个部分恰好是a的方案总数。
用表示把b做任意剖分,其中最大的一个部分不大于a的方案总数。
那么,有:
。
消去,有:
我们可以按照a、b递增的顺序计算的值,这样在在计算的时候,和都已经得到,故我们只用进行一次简单的加法运算即可。
最后的即为所求。
这种方法的时间复杂度为。
空间复杂度为O(nk),或者我们可以用滚动数组的方法降到O(n)。
该题中模型转化的思想,是很值得借鉴的。
给出一个长度不超过200的由小写英文字母组成的字符串(约定:
该字符串以每行20个字母的方式输入,且保证每行一定20个)。
要求将此字符串分成k份(1<
k≤40),且每份中包含的单词个数加起来最大(每份中包含的单词可以重叠。
当选用一个单词之后,其第一个字母不能再用。
例如字符串this中可以包含this和is,但是选用this之后就不能再选th)。
这一题应该算是一道比较原创的题目。
注意到题目中有一个非常特殊的地方,就是以串中某个位置的字母为首字母,最多只能分出一个单词。
由于在拆分字符串的过程中,如果以某位置为首某个较短单词被截断,那么以该位置为首的较长单词必然也会被截断。
也就是说,对于各个位置来说我们选取较短的单词总不会比选取较长的单词所形成的单词少。
这样我们可以定义一个在位置的参数表示以位置的字母为首字母,所能形成的最短单词的长度。
这样如果在这个位置加上这个单词的长度之内截断,则以该位置为首字母就不能形成单词,否则就可能形成一个单词。
这样对于所有的不同个首位置,我们只要在各个位置依次对各个单词进行匹配就以得出所对应的的值,这一部分的复杂度为O(wl2)。
然后是计算把字串分为多个部分的过程中有什么单词可以留下。
由于是把字串分为多个部分,故我们类比其他的分割字串的问题,列出动态规划的方程如下:
这里有初值为:
这个式子中,表示把字串前个字符分成段时所形成的最多单词的数目,表示字串的第个字符开始的个字符形成的字串中所能形成的单词数。
这里由于过于庞大,不能用预计算的方法加快速度,只能现用现算。
计算的方法为对于所有的,如果存在(也就是有单词可以在位置匹配上),且,则该位置必然可以匹配上一个单词。
把所有这样位置的数目加起来就可以得到的值了。
这样算法的复杂度为O(kl3)。
但这里计算还有一个技巧,就是我们可以依次按照增加,增加,增加的顺序计算的值。
这样在由增加到的时候,由于在计算所对应的值时可以用
这个方程进行复杂度为O
(1)的递推计算,故总复杂度可以降到O(kl2+wl2)。
这种被称作双重动态规划的方法,请读者自己好好体会。
CAR的旅行路线
又到暑假了,住在城市A的Car想和朋友一起去城市B旅游。
她知道每个城市都有四个飞机场,分别位于一个矩形的四个顶点上,同一个城市的两个机场之间有一条笔直的高速铁路,第i个城市高速铁路的单位里程价格为Ti。
任意两个不同城市的机场之间均有航线,所有航线单位里程的价格均为t。
那么Car应如何安排到B的路线才能尽可能的节省花费呢?
她发现这并不是一个简单的问题,于是她来向你请教。
我们换一种对题目描述的方法,就是给出一张地图,求出某人从某点到另一点的最短距离。
这是一个图论中的标准问题,就是在一个无向图中求最短路径的问题。
首先,这个人在从起点到终点所可能停下的点都是确定的,就是一个城市的四个机场(其他的时候是没有更换交通工具的自由的)。
所以我们可以以所有的机场为顶点,而机场与机场之间的交通路线为边建立一个图。
该图的边权值为机场之间相互通行所需的时间。
至于求最短路,我们可以使用一个被称为Dijkstra的算法。
该算法的主要思想就是模拟液体等速的在管子内流动,先流到的位置就是离起点较近的位置。
在使用算法实现的时候,我们可以把顶点划分为已流到的和未流到的两个部分。
依次找出液体从已流到的部分最少还需经过多少时间才能流到未流到的部分,并模拟流的过程。
有关该算法的具体内容,请读者自行参见有关图论的算法书籍,这里不赘述。
最后,还需注意的是题目中对于一个城市只给出了三个机场的坐标。
但我们知道这四个机场是成矩形排列的,而矩形的对角线互相平分。
故我们可以先找出这三个机场中相对的两个机场,易知这样的机场就是距离最大的两个机场。
然后通过对这两个机场求平均数求出该矩形的中心,再把另一个机场按矩形的中心作对称就可以得出第四个机场的坐标。
还有题目中最多可能有400个节点,也就是说可能有80000条边。
这些边显然是无法事先计算并保存下来的。
但由于在求最短路径的过程中,每一条边最多只会访问两遍,故我们可以采用现用现算的办法。
其他在计算点与点之间距离时也要注意对整数取平方时的运算有可能引发整数越界的问题,我们应该先转换成实数再进行计算。
该算法的时间复杂度为,空间复杂度为O(n)。
关于yuwei和zhaoshuang的报告的一点补充
其实最简单的方法是这样子的:
让x从–100.00到100.00枚举一下,得到20000个f(x),取跟0最接近的三个f(x),对应的x就是答案了。
(提示有时候会扰乱别人的思维)
如果时间不是很严格的话,枚举还是能过的。
这跟我在分区赛前出的一道模拟试题方法类似。
不过分区这题的数据巨弱,居然只输出”有多少个位置开始至少可以有一个单词”也能对4/5的点!
真是佩服出数据的人……
标准算法的题目,不过这题算老题目了……算费用的时候,令A,B城市内的路费为0的话,就可以直接得到结果,而不需要做4遍Dijkstra。
当然,用Flody也可以做
2002年全国青少年信息学(计算机)奥林匹克分区联赛复赛提高组试题解题报告
题一均分纸牌(存盘名NOIPG1)
如果你想到把每堆牌的张数减去平均张数,题目就变成移动正数,加到负数中,使大家都变成0,那就意味着成功了一半!
拿例题来说,平均张数为10,原张数9,8,17,6,变为-1,-2,7,-4,其中没有为0的数,我们从左边出发:
要使第1堆的牌数-1变为0,只须将-1张牌移到它的右边(第2堆)-2中;
结果是-1变为0,-2变为-3,各堆牌张数变为0,-3,7,-4;
同理:
要使第2堆变为0,只需将-3移到它的右边(第3堆)中去,各堆牌张数变为0,0,4,-4;
要使第3堆变为0,只需将第3堆中的4移到它的右边(第4堆)中去,结果为0,0,0,0,完成任务。
每移动1次牌,步数加1。
也许你要问,负数张牌怎么移,不违反题意吗?
其实从第i堆移动-m张牌到第i+1堆,等价于从第i+1堆移动m张牌到第i堆,步数是一样的。
如果张数中本来就有为0的,怎么办呢?
如0,-1,-5,6,还是从左算起(从右算起也完全一样),第1堆是0,无需移牌,余下与上相同;
再比如-1,-2,3,10,-4,-6,从左算起,第1次移动的结果为0,-3,3,10,-4,-6;
第2次移动的结果为0,0,0,10,-4,-6,现在第3堆已经变为0了,可节省1步,余下继续。
程序清单
programNOIPG1;
constmaxn=100;
vari,j,n,step:
integer;
ave:
longint;
a:
array[1..maxn]ofinteger;
f:
text;
filename:
string;
begin
write('
Inputfilename:
'
);
readln(filename);
assign(f,filename);
reset(f);
readln(f,n);
=0;
step:
=1tondobegin
read(f,a[i]);
inc(ave,a[i]);
end;
ave:
=avedivi;
=1tondoa[i]:
=a[i]-ave;
i:
=1;
j:
=n;
whilea[i]=0doinc(i);
{过滤左边的0}
whilea[j]=0dodec(j);
{过滤右边的0}
while(i<
j)dobegin
inc(a[i+1],a[i]);
{将第i堆牌移到第i+1堆中去}
a[i]:
{第i堆牌移走后变为0}
inc(step);
{移牌步数计数}
inc(i);
{对下一堆牌进行循环操作}
{过滤移牌过程中产生的0}
writeln(step);
end.
点评:
基本题(较易)本题有3点比较关键:
一是善于将每堆牌数减去平均数,简化了问题;
二是要过滤掉0(不是所有的0,如-2,3,0,-1中的0是不能过滤的);
三是负数张牌也可以移动,这是辩证法(关键中的关键)。
题二字串变换(存盘名:
NOIPG2)
本题是典型的广度优先搜索的例子,但如果只采用正向搜索,某些情况下计算量过大,速度过慢,故采取双向搜索且判重并适当剪枝,效果较好。
{$A-,B-,D-,E-,F-,G-,I-,L-,N-,O-,P-,Q-,R-,S-,T-,V-,X-,Y-}
{$M8192,0,655360}
programNOIPG2;
constmaxn=2300;
type
node=record{定义节点数据类型}
str:
string[115];
dep:
byte;
{str表示字串,其长度不会超过115(长度超过115的字串
不可能通过变换成为目标字串,因为题目限定变换10次之内,且串长
不超过20,即起始串最多可经过5次变换时增长,中间串的最大长度
为20+5*19=115,否则经过余下的步数不可能变为长度不超过20的
目标串),dep表示深度}
ctype=array[1..maxn]of^node;
bin=0..1;
var
maxk:
c:
array[0..1]ofctype;
x0:
array[0..6,0..1]ofstring[20];
filename:
open,closed:
array[0..1]ofinteger;
procedureInit;
{读取数据,初始化}
varf:
temp:
i,j:
=0to1do
forj:
=1tomaxndonew(c[i,j]);
i:
whilenoteof(f)and(i<
=6)dobegin
readln(f,temp);
x0[i,0]:
=copy(temp,1,pos('
'
temp)-1);
x0[i,1]:
=copy(temp,pos('
temp)+1,length(temp));
=i-1;
close(f);
procedurecalc;
vari,j,k:
st:
bin;
d:
f:
procedurebool(st:
bin);
{判断是否到达目标状态或双向搜索相遇}
vari:
ifx0[0,1-st]=c[st,closed[st]]^.strthenbegin
{如果到达目标状态,则输出结果,退出}
writeln(c[st,closed[st]]^.dep);
halt;
=1toclosed[1-st]do
ifc[st,closed[st]]^.str=c[1-st,i]^.strthenbegin
{如果双向搜索相遇(即得到同一节点),
则输出结果(2个方向搜索的步数之和),退出}
writeln(c[st,closed[st]]^.dep+c[1-st,i]^.dep);
procedurecheckup(st:
{判断节点是否与前面重复}
=1toclosed[st]-1do
ifc[st,i]^.str=c[st,closed[st]]^.strthenbegin
dec(closed[st]);
exit;
{如果节点重复,则删除本节点}
bool(st);
{如果节点不重复,再判断是否到达目标状态}
procedureexpand(st:
{扩展产生新节点}
vari,j,k,lx,ld:
inc(open[st]);
d:
=c[st,open[st]]^.str;
{队首节点出队}
k:
=c[st,open[st]]^.dep;
ld:
=length(d);
=1tomaxkdobegin
{从队首节点(父节点)出发产生新节点(子节点)}
lx:
=length(x0[i,st]);
=1tolddobegin
if(copy(d,j,lx)=x0[i,st])and(length(copy(d,1,j-1)+x0[i,1-st]
+copy(d,j+lx,ld))<
=115)thenbegin
{如果新节点的串长超过115,则不扩展!
即剪掉此枝}
ifclosed[st]>
=maxnthenexit;
{如果队列已满,只好退出}
inc(closed[st]);
{新节点入队}
c[st,closed[st]]^.str:
=copy(d,1,j-1)+x0[i,1-st]+copy(d,j+lx,ld);
c[st,closed[st]]^.dep:
=k+1;
{子节点深度=父节点深度+1}
checkup(st);
{检查新节点是否重复}
Begin
forst:
=0to1