版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
1、NOIP2004解题报告津津的储蓄计划 <问题描述> 津津的零花钱一直都是自己管理。每个月的月初妈妈给津津300元钱,津津会预算这个月的花销,并且总能做到实际花销和预算的相同。 为了让津津学习如何储蓄,妈妈提出,津津可以随时把整百的钱存在她那里,到了年末她会加上20%还给津津。因此津津制定了一个储蓄计划:每个月的月初,在得到妈妈给的零花钱后,如果她预计到这个月的月末手中还会有多于100元或恰好100元,她就会把整百的钱存在妈妈那里,剩余的钱留在自己手中。
2、0; 例如11月初津津手中还有83元,妈妈给了津津300元。津津预计11月的花销是180元,那么她就会在妈妈那里存200元,自己留下183元。到了11月月末,津津手中会剩下3元钱。 津津发现这个储蓄计划的主要风险是,存在妈妈那里的钱在年末之前不能取出。有可能在某个月的月初,津津手中的钱加上这个月妈妈给的钱,不够这个月的原定预算。如果出现这种情况,津津将不得不在这个月省吃俭用,压缩预算。 现在请你根据2004年1月到12月每个月津津的预算,判断会不会出现这种情况。如果不会,计算到2004年年
3、末,妈妈将津津平常存的钱加上20还给津津之后,津津手中会有多少钱。 - 输入文件 输入文件save.in包括12行数据,每行包含一个小于350的非负整数,分别表示1月到12月津津的预算。 - 输出文件 输出文件save.out包括一行,这一行只包含一个整数。如果储蓄计划实施过程中出现某个月钱不够用的情况,输出-X,X表示出现这种情况的第一个月;否则输出到2004年年末津津手中会有多少钱。 - 样例输入1290/230/280/200/300/170/340/50/90/80/200/60- 样例输出1-
4、7 - 样例输入2290/230/280/200/300/170/330/50/90/80/200/60 - 样例输出21580<算法分析>这是本次分区联赛当中最简单的题,算法也很简单:模拟法。每个月把津津手上的钱加上妈妈给的300元,再减去预算,得到当前手中的钱,假如这个钱的值是负数(出现亏损),那么就输出负的月数,接着算出存入的钱,并且将手中的钱减去。如此往复,直到最后按要求输出结果或者中间已经停止。可以边读边处理,也可以把每个月的收入都读入然后处理。模拟时只需要记录当钱手中的钱和已存入的钱即可。时间、空间复杂度均为常数。处理这种题目关键是要细心,不要丢了冤枉分。<程序代
5、码>program save;const inputfilename='save.in' outputfilename='save.out' maxmonth=12; monthget=300;var plan:array1.maxmonthof longint; ans:longint;procedure read_data;var i:integer;begin assign(input,inputfilename); reset(input); for i:=1 to maxmonth do readln(plani); close(input);e
6、nd;procedure proceed;var tim,now,bank:longint;begin now:=0;bank:=0; for tim:=1 to maxmonth do begin now:=now+monthget; now:=now-plantim; if now<0 then begin ans:=-tim; exit; end; bank:=bank+now div 100; now:=now mod 100; end; ans:=now+bank*120;end;procedure answer;begin assign(output,outputfilena
7、me); rewrite(output); writeln(ans); close(output);end;begin read_data; proceed; answer;end.合并果子<问题描述> 在一个果园里,多多已经将所有的果子打了下来,而且按果子的不同种类分成了不同的堆。多多决定把所有的果子合成一堆。 每一次合并,多多可以把两堆果子合并到一起,消耗的体力等于两堆果子的重量之和。可以看出,所有的果子经过n-1次合并之后,就只剩下一堆了。多多在合并果子时总共消耗的体力等于每次合并
8、所耗体力之和。 因为还要花大力气把这些果子搬回家,所以多多在合并果子时要尽可能地节省体力。假定每个果子重量都为1,并且已知果子的种类数和每种果子的数目,你的任务是设计出合并的次序方案,使多多耗费的体力最少,并输出这个最小的体力耗费值。 例如有3种果子,数目依次为1,2,9。可以先将1、2堆合并,新堆数目为3,耗费体力为3。接着,将新堆与原先的第三堆合并,又得到新的堆,数目为12,耗费体力为12。所以多多总共耗费体力=3+12=15。可以证明15为最小的体力耗费值。 - 输入文件
9、; 输入文件fruit.in包括两行,第一行是一个整数n(1<n<=10000),表示果子的种类数。第二行包含n个整数,用空格分隔,第i个整数ai(1<ai<=20000)是第i种果子的数目。 - 输出文件 输出文件fruit.out包括一行,这一行只包含一个整数,也就是最小的体力耗费值。输入数据保证这个值小于231。 - 样例输入3 1 2 9 - 样例输出15 - 数据规模对于30的数据,保证有n<=1000: 对于50的数据,保证有n<=5000; 对于全部的数据,保证有n<=1
10、0000。<算法分析>首先注意到一个很关键的地方,就是每次合并的两堆果子不一定相邻。也就是说这个问题与经典的石子归并是不同的。把初始时的每堆果子看成一个叶子节点,重量为wi,则合并后的堆相当于一棵子树。合并两棵子树相当于把这两棵子树中所有的叶子节点的深度di增加1,而最后的费用就是(wi + di)。这时我们突然发现,这就是经典的Huffman树构造问题。构造Huffman树问题当中需要执行的操作是:(1) 从一个集合中取出最小的数;(2) 插入一个数字到这个集合中。支持动态GetMin和Insert操作的数据结构,我们可以选择用堆来实现。堆是一种完全二叉树,且保证根结点的值小于等
11、于其子孙结点。具体实现方法可以参见本书的数据结构部分。于是整体算法的时间复杂度为O(nlogn),空间复杂度为O(n)。但是,有没有更好的方法呢?很显然,每次合并两个结点以后,得到的大小是严格递增的。于是我们可以维护两个队列QA与QB,QA记录叶子结点的权值wi,QB记录子树的权值。这样,每次就一定是在QA和QB的队首取数,在QA和QB的队尾删除。这样时间复杂度就降到了O(n)。因为ai <= 20000,所以排序也可以用计数排序在O(n)时间内实现,整体时间复杂度仅为O(n)。<程序代码>program fruit;const inputfilename='frui
12、t.in' outputfilename='fruit.out' maxn=30005;var n,tot,ans:longint; a:array1.maxnof longint;procedure read_data;var i:longint;begin assign(input,inputfilename); reset(input); readln(n); for i:=1 to n do read(ai); close(input);end;procedure sort(l,r:longint);var i,j:longint; x,y:longint;be
13、gin i:=l;j:=r; x:=a(l+r) shr 1; repeat while ai<x do i:=i+1; while aj>x do j:=j-1; if i<=j then begin y:=ai;ai:=aj;aj:=y; i:=i+1;j:=j-1; end; until i>j; if l<j then sort(l,j); if i<r then sort(i,r);end;procedure swap(i,j:longint);var t:longint;begin t:=ai;ai:=aj;aj:=t;end;procedure
14、 insert_node(x:longint);var i,j:longint;begin tot:=tot+1; atot:=x; i:=tot; while i>1 do begin j:=i shr 1; if ai<aj then begin swap(i,j); i:=j; end else i:=1; end;end;function delete_min:longint;var i,j:longint;begin delete_min:=a1; swap(1,tot); tot:=tot-1; i:=1; while i<=tot shr 1 do begin
15、j:=i+i; if (j<tot) and (aj+1<aj) then j:=j+1; if aj<ai then begin swap(i,j); i:=j; end else i:=tot; end;end;procedure proceed;var i,tmp:longint;begin tot:=n;ans:=0; for i:=1 to n-1 do begin tmp:=delete_min+delete_min; ans:=ans+tmp; insert_node(tmp); end;end;procedure answer;begin assign(out
16、put,outputfilename); rewrite(output); writeln(ans); close(output);end;begin read_data; sort(1,n); proceed; answer;end.合唱队形<问题描述> N位同学站成一排,音乐老师要请其中的(N-K)位同学出列,使得剩下的K位同学排成合唱队形。 合唱队形是指这样的一种队形:设K位同学从左到右依次编号为1,2,K,他们的身高分别为T1,T2,TK, 则他们的身高满足T1<.<Ti>Ti+1>
17、>TK(1<=i<=K)。 你的任务是,已知所有N位同学的身高,计算最少需要几位同学出列,可以使得剩下的同学排成合唱队形。- 输入文件 输入文件chorus.in的第一行是一个整数N(2<=N<=100),表示同学的总数。第一行有n个整数,用空格分隔,第i个整数Ti(130<=Ti<=230)是第i位同学的身高(厘米)。- 输出文件 输出文件chorus.out包括一行,这一行只包含一个整数,就是最少需要几位同学出列
18、。- 样例输入8186 186 150 200 160 130 197 220- 样例输出4- 数据规模对于50的数据,保证有n<=20;对于全部的数据,保证有n<=100。<算法分析>假设已经确定了最高的人的位置P,那么问题就转化为求1-P的最长上升子序列与P-N的最长下降子序列。设lefti表示从左边开始到i结束的最长上升子序列长度,righti表示从右边开始到i结束的最长上升子序列长度。则答案即为N-maxlefti+righti-1,其中1<=i<=N并且lefti,righti>=2,这是因为最高的人不能在合唱队形的最左边或者最右边。现在关键
19、的问题转化为求lefti与righti。由于两者的意义类似,只是方向不同,因此只介绍lefti的求法。可以用动态规划求lefti,方程为lefti=maxleftj+1,其中0<=j<=i-1且Ti>Tj。边界条件为left0=0。这样的复杂度为O(N2),在时限内出解绰绰有余。但事实上本题还有O(NlogN)的算法,请参见本书的动态规划部分。<程序代码>program chorus;const inputfilename='chorus.in' outputfilename='chorus.out' maxn=100;var n,
20、min:integer; t:array1.maxnof longint; up,down:array1.maxnof longint;procedure read_data;var i:integer;begin assign(input,inputfilename); reset(input); readln(n); for i:=1 to n do read(ti); close(input);end;procedure proceed;var i,j:integer;begin for i:=1 to n do begin upi:=1; downi:=1; end; for i:=2
21、 to n do for j:=1 to i-1 do if (upi<upj+1) and (tj<ti) then upi:=upj+1; for i:=n-1 downto 1 do for j:=i+1 to n do if (downi<downj+1) and (ti>tj) then downi:=downj+1; min:=0; for i:=1 to n do if min<upi+downi-1 then min:=upi+downi-1; min:=n-min;end;procedure answer;begin assign(output,
22、outputfilename); rewrite(output); writeln(min); close(output);end;begin read_data; proceed; answer;end.虫食算<问题描述> 所谓虫食算,就是原先的算式中有一部分被虫子啃掉了,需要我们根据剩下的数字来判定被啃掉的字母。来看一个简单的例子: 43#9865#045 + 8468#6633 &
23、#160;其中#号代表被虫子啃掉的数字。根据算式,我们很容易判断:第一行的两个数字分别是5和3,第二行的数字是5。 现在,我们对问题做两个限制: 首先,我们只考虑加法的虫食算。这里的加法是N进制加法,算式中三个数都有N位,允许有前导的0。 其次,虫子把所有的数都啃光了,我们只知道哪些数字是相同的,我们将相同的数字用相同的字母表示,不同的数字用不同的字母表示。如果这个算式是N进制的,我们就取英文字母表午的前N个大写字母来表示这个算式中的0到N-1这N个不同的数
24、字:但是这N个字母并不一定顺序地代表0到N-1)。输入数据保证N个字母分别至少出现一次。 BADC + CRDA DCCC 上面的算式是一个4进制的算式
25、。很显然,我们只要让ABCD分别代表0123,便可以让这个式子成立了。你的任务是,对于给定的N进制加法算式,求出N个不同的字母分别代表的数字,使得该加法算式成立。输入数据保证有且仅有一组解,- 输入文件 输入文件alpha.in包含4行。第一行有一个正整数N(N<=26),后面的3行每行有一个由大写字母组成的字符串,分别代表两个加数以及和。这3个字符串左右两端都没有空格,从高位到低位,并且恰好有N位。- 输出文件 输出文件alpha.out包含一行。在这一行中,应当包含唯一的那组解。解是这样表示的
26、:输出N个数字,分别表示A,B,C所代表的数字,相邻的两个数字用一个空格隔开,不能有多余的空格。- 样例输入5ABCEDBDACEEBBAA- 样例输出1 0 3 4 2- 数据规模对于30的数据,保证有N<10;对于50的数据,保证有N<15;对于全部的数据,保证有N<=26。<算法分析> 显然这是一道求可行解的搜索题。对于这种题,卡点是没有任何意义的,因为找不到解的话肯定会输出错误结果。最单纯的搜索即是枚举数字与字母直接的对应关系。由组合数学知识知时间复杂度为O(n!*n),严重超时,需要优化。我们知道计算机是很“笨”的,它不会思考,在盲目搜索的过程中,很容易
27、出现这种情况:计算机在某一位搜索出了一个算式1 + 1 = 3,并且继续搜索。人眼很容易就看出这是不合法的,但计算机不会。于是,我们想到了第一个剪枝:每次搜索的时候,从最后向前判断是否有不合法的式子。这一个剪枝非常简单,但是效果却非常的好。为了配合这一种剪枝更好的实行,搜索顺序的改变也成为大大提高程序效率的关键:从右往左,按照字母出现顺序搜索,有很大程度上提高了先剪掉废枝的情况,使程序的效率得到大大的提高。有了以上两个剪枝,程序就已经可以通过大部分测试点了。但是有没有更多的剪枝呢?答案是肯定的。根据前面的剪枝,我们可以找到类似的几个剪枝:对于a + b = c的形式,假如:A*?*+ B*?*
28、?*C*?*其中*代表已知,?代表未知。那么,A + B与C的情况并不能直接确定。但是,假如(A + B) mod N与(A + B + 1) mod N都不等于C的话,那么这个等式一定是不合法的。因为它只有进位和不进位的两种情况。同样,我们在一个数组里记录了Usedi表示一个数字有没有用过,那么,对于某一位A + B = C的等式,如果已经得到了两个数,另一个数还待搜索的时候,我们还可以根据这个加入一个剪枝:例如A + ? = C的形式,考虑不进位的情况,则?处为P1 = (C - A + N) mod N假如考虑进位的情况,则?处为P2 = (C - A - 1 + N) mod N假如P
29、1、P2均被使用过,那么这个搜索一定是无效的,可以剪去。有了以上的剪枝,就可以很轻松地通过所有的测试数据了。当然,还有很多值得思考的剪枝以及其他的思路,例如枚举进位、解方程(但是可能需要枚举)等,在这里就不详细讨论了。<程序代码> program alpha;const inputfilename='alpha.in' outputfilename='alpha.out' maxn=26;var n:integer; b:array1.3,1.maxnof integer; d:array1.maxnof integer; used:array0.m
30、axnof boolean;procedure read_(var x:integer);var c:char;begin read(c); x:=ord(c)-ord('A')+1;end;procedure read_data;var i,j:integer;begin assign(input,inputfilename); reset(input); readln(n); for i:=1 to 3 do begin for j:=1 to n do read_(bij); readln; end; close(input);end;procedure answer;var i:integer;begin assign(output,outputfilename); rewrite(output); for i:=1 to n-1 do write(di,' '); writeln(dn); close(output); halt;end;function div_(x:integer)
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 大型广告牌安装吊车租赁合同
- 电视剧制作团队制片人招聘协议
- 一卡通系统订货合同
- 建设工程施工合同地热能开发
- 企业内部网站管理办法
- 水电站土地开发合同
- 电子产品生产废标条件研究
- 酒店维护工程合同
- 矿山安全质量管理办法
- 企业产品演示员操作手册
- 第六课 售中订单处理
- 人教版(PEP)四年级上册英语unit 1 My classroom图文完美版(课堂PPT)
- 幼小衔接中存在的问题及对策
- 工程前沿案例作业
- 中级汉语期末考试测试题(共5页)
- 《国家电网公司安全生产事故隐患排查治理管理办法》(国家电网安监[
- 水保监理报告范文
- xx售楼部钢结构及玻璃幕墙工程拆除施工方案
- 云南沿边高校青年教师发展现状及问题分析
- 先进制造业项目专项资金申请报告范文模板
- OOK调制解调电路设计
评论
0/150
提交评论