切换到宽版
  • 9039阅读
  • 7回复

版主,能不能上传些c语言版提高组的解题报告阿? [复制链接]

上一主题 下一主题
离线canyvv
 
只看楼主 倒序阅读 0 发表于: 2006-11-14
版主,能不能上传些c语言版的提高组的解题报告阿?
离线zhuang
只看该作者 1 发表于: 2006-11-14
我有一份04的,不知道有没有用。
第十届全国青少年信息学奥林匹克联赛复赛解题报告一


津津的储蓄计划 解题报告

<问题描述>

  津津的零花钱一直都是自己管理。每个月的月初妈妈给津津300元钱,津津会预算这个月的花销,并且总能做到实际花销和预算的相同。

  为了让津津学习如何储蓄,妈妈提出,津津可以随时把整百的钱存在她那里,到了年末她会加上20%还给津津。因此津津制定了一个储蓄计划:每个月的月初,在得到妈妈给的零花钱后,如果她预计到这个月的月末手中还会有多于100元或恰好100元,她就会把整百的钱存在妈妈那里,剩余的钱留在自己手中。

  例如11月初津津手中还有83元,妈妈给了津津300元。津津预计11月的花销是180元,那么她就会在妈妈那里存200元,自己留下183元。到了11月月末,津津手中会剩下3元钱。

  津津发现这个储蓄计划的主要风险是,存在妈妈那里的钱在年末之前不能取出。有可能在某个月的月初,津津手中的钱加上这个月妈妈给的钱,不够这个月的原定预算。如果出现这种情况,津津将不得不在这个月省吃俭用,压缩预算。

  现在请你根据2004年1月到12月每个月津津的预算,判断会不会出现这种情况。如果不会,计算到2004年年末,妈妈将津津平常存的钱加上20%还给津津之后,津津手中会有多少钱。

- 输入文件

  输入文件save.in包括12行数据,每行包含一个小于350的非负整数,分别表示1月到12月津津的预算。

- 输出文件

  输出文件save.out包括一行,这一行只包含一个整数。如果储蓄计划实施过程中出现某个月钱不够用的情况,输出-X,X表示出现这种情况的第一个月;否则输出到2004年年末津津手中会有多少钱。

- 样例输入1
290/230/280/200/300/170/340/50/90/80/200/60

- 样例输出1

-7

- 样例输入2

290/230/280/200/300/170/330/50/90/80/200/60

- 样例输出2

1580



<算法分析>

这是本次分区联赛当中最简单的题,算法也很简单:模拟法。

每个月把津津手上的钱加上妈妈给的300元,再减去预算,得到当前手中的钱,假如这个钱的值是负数(出现亏损),那么就输出负的月数,接着算出存入的钱,并且将手中的钱减去。如此往复,直到最后按要求输出结果或者中间已经停止。



<数据结构>

边读边处理。只需要记录当钱手中的钱和已存入的钱即可。时间、空间复杂度均为常数。



<代码清单C语言>

#include <fstream>

using namespace std;



ifstream fin("save.in");

ofstream fout("save.out");



void init() {

  int p, save = 0, cnt = 0;



  for (int i = 1; i <= 12; i ++) {

    fin >> p;

    cnt = cnt + 300 - p;

    while (cnt >= 100) {

      save += 100;

      cnt -= 100;

    }

    if (cnt < 0) {

      fout << - i << endl;

      return;

    }

  }

  fout << cnt + int(save * 1.2) << endl;

}



int main() {

  init();

  return 0;

}
...多思考,少说话...
离线zhuang
只看该作者 2 发表于: 2006-11-14
第十届全国青少年信息学奥林匹克联赛复赛解题报告二


合并果子 解题报告

<问题描述>

  在一个果园里,多多已经将所有的果子打了下来,而且按果子的不同种类分成了不同的堆。多多决定把所有的果子合成一堆。

  每一次合并,多多可以把两堆果子合并到一起,消耗的体力等于两堆果子的重量之和。可以看出,所有的果子经过n-1次合并之后,就只剩下一堆了。多多在合并果子时总共消耗的体力等于每次合并所耗体力之和。

  因为还要花大力气把这些果子搬回家,所以多多在合并果子时要尽可能地节省体力。假定每个果子重量都为1,并且已知果子的种类数和每种果子的数目,你的任务是设计出合并的次序方案,使多多耗费的体力最少,并输出这个最小的体力耗费值。

  例如有3种果子,数目依次为1,2,9。可以先将1、2堆合并,新堆数目为3,耗费体力为3。接着,将新堆与原先的第三堆合并,又得到新的堆,数目为12,耗费体力为12。所以多多总共耗费体力=3+12=15。可以证明15为最小的体力耗费值。

- 输入文件

  输入文件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<=10000。



<算法分析>

将这个问题换一个角度描述:给定n个叶结点,每个结点有一个权值W,将它们中两个、两个合并为树,假设每个结点从根到它的距离是D,使得最终∑(wi + di)最小。

于是,这个问题就变为了经典的Huffman树问题。Huffman树的构造方法如下:

(1)         从森林里取两个权和最小的结点

(2)         将它们的权和相加,得到新的结点,并且把原结点删除,将新结点插入到森林中

(3)         重复(1),直到整个森林里只有一棵树。

这个方法的正确性可以参见数据结构。



<数据结构>

很显然,问题当中需要执行的操作是:(1) 从一个表中取出最小的数 (2) 插入一个数字到这个表中。

支持动态Extract_Min和Insert操作的数据结构,我们可以选择用堆来实现。堆是一种完全二叉树,且保证根结点的值严格大于(或小于)其子孙结点。具体实现方法可以参见数据结构。

于是整体算法的时间复杂度为O(nlogn),空间复杂度为O(n)。



但是,有没有更好的方法呢?很显然,每次合并两个结点以后,得到的大小是严格递增的,于是我们可以维护两个表,一个是原数字A,一个是新加入的数字B。这样,每次就一定是在A和B的头部取数,在A和B的尾部删除。这样,时间复杂度就降到了O(n)。因为a <= 20000,所以排序也可以用o(20000)的方法来实现,整体时间复杂度为O(n)。(感谢BCBill提供这个方法)



<代码清单C语言>

#include <fstream>

#include <list>

#include <algorithm>

using namespace std;



ifstream fin("fruit.in");

ofstream fout("fruit.out");



int n;

list <int> a, b;



void init() {

  int p;



  fin >> n;

  for (int i = 0; i < n; i ++) {

    fin >> p;

    a.push_back(p);

  }

  a.sort();

}



int get() {

  int ans;



  if (a.empty()) {

    ans = b.front(); b.pop_front(); return ans;

  }

  if (b.empty()) {

    ans = a.front(); a.pop_front(); return ans;

  }

  if (a.front() < b.front()) {

    ans = a.front(); a.pop_front(); return ans;

  }

  else {

    ans = b.front(); b.pop_front(); return ans;

  }

}

   

void work() {

  int p, sum = 0;

  for (int i = 0; i < n - 1; i ++) {

    p = get() + get();

    b.push_back(p);

    sum += p;

  }

  fout << sum << endl;

}



int main() {

  init();

  work();

  return 0;

}
...多思考,少说话...
离线zhuang
只看该作者 3 发表于: 2006-11-14
第十届全国青少年信息学奥林匹克联赛复赛解题报告三


合唱队形 解题报告

<问题描述>

N位同学站成一排,音乐老师要请其中的(N-K)位同学出列,使得剩下的K位同学排成合唱队形。
  合唱队形是指这样的一种队形:设K位同学从左到右依次编号为1,2…,K,他们的身高分别为T1,T2,…,TK, 则他们的身高满足T1<...<Ti>Ti+1>…>TK(1<=i<=K)。
  你的任务是,已知所有N位同学的身高,计算最少需要几位同学出列,可以使得剩下的同学排成合唱队形。
- 输入文件
  输入文件chorus.in的第一行是一个整数N(2<=N<=100),表示同学的总数。第一行有n个整数,用空格分隔,第i个整数Ti(130<=Ti<=230)是第i位同学的身高(厘米)。
- 输出文件
  输出文件chorus.out包括一行,这一行只包含一个整数,就是最少需要几位同学出列。
- 样例输入
8
186 186 150 200 160 130 197 220
- 样例输出
4
- 数据规模
对于50%的数据,保证有n<=20;
对于全部的数据,保证有n<=100。



<算法分析>

动态规划。最基本的想法是:枚举中间最高的一个人,接着对它的左边求最长上升序列(注意序列中最高的同学不应高过基准),对右边求最长下降序列(同样的,序列中最高的同学不应高过基准)。时间复杂度为O(n^3),算法实现起来也很简单。

接着对这个算法进行分析,我们不难发现,假如还是基于枚举一个同学的话,设Incsq表示了1 - i的最长上升序列,Decsq表示了i - n的最长下降序列,那么,

Current = Incsq + Decsq - 1(两个数组中i被重复计算了)

那么,我们只需要先求好最长上升和下降序列,然后枚举中间最高的同学就可以了。



<算法优化>

求最长上升序列的经典状态转移方程为:

opt = max{opt[j]+1, 其中i<j<=n, 且list[j]>list}

我们对状态转移方程稍微做一些修改:

opt = max{opt[i+1], min{j, rec[j]>=list}}

rec[j] = list

很明显可以看出,在opt的寻找j的过程当中,查询序列是单调的,于是可以用二分法,就十分巧妙地在logn的时间内找到指定的j,而问题的总体复杂度为O(nlogn)。这样,这个问题的算法效率就得到了大幅度的提升,即便n是106,也可以轻松应对。



<代码清单C语言>

#include <fstream>

#include <cstring>

using namespace std;



ifstream fin("chorus.in");

ofstream fout("chorus.out");



const int maxn = 100;



int n, a[maxn];

int incsq[maxn], decsq[maxn];



void init() {

  fin >> n;

  for (int i = 0; i < n; i ++)

    fin >> a;

}



void LIncSeq()

{

  int i, low, high, mid, ans = 0;

  int sol[maxn];



  for (i = 0; i < n; i ++) {

    low = 1; high = ans;

    while (low <= high) {

      mid = (low + high) >> 1;

      if (sol[mid] < a) low = mid + 1;

      else high = mid - 1;

    }

    if (low > ans) ans ++;

    sol[low] = a;

    incsq = ans;

  }

}



void LDecSeq()

{

  int i, low, high, mid, ans = 0;

  int sol[maxn];



  for (i = 0; i < n; i ++) {

    low = 1; high = ans;

    while (low <= high) {

      mid = (low + high) >> 1;

      if (sol[mid] > a) low = mid + 1;

      else high = mid - 1;

    }

    if (low > ans) ans ++;

    sol[low] = a;

    decsq = ans;

  }

}



void work() {

  int i, max = 0;



  LIncSeq();

  LDecSeq();



  for (i = 0; i < n; i ++)

    if (incsq + decsq - 1 > max)

      max = incsq + decsq - 1;



  fout << n - max << endl;

}



int main() {

  init();

  work();

  return 0;

}
...多思考,少说话...
离线canyvv
只看该作者 4 发表于: 2006-11-15
xiexie
离线canyvv
只看该作者 5 发表于: 2006-11-15
NOIP2005信息学奥林匹克分区联赛解题报告(湖南)C 语言版
第一题:谁拿了最多的奖学-Scholar
[问题评估]
这个题目据问题本身而言是相当简单的,没有牵涉到过多的算法,属于普及型试题。同时也是对实际问题一种分析和判断。总的来看,本题在方向上,向现实问题迈出了一步,是信息学和生活有了更多的联系。
问题的算法是模拟。当中唯一的难点就是数据处理,考察点为数据库的建立和统计。
[程序实现]
  由于程序数据范围只有100,当中不牵涉到数据移动,所以用一个纪录型数组,或者多个数组均可,在这里我们使用纪录型来描述。
  对于输入数据有两种方式来实现。
    法一〉逐个字符累加。
        首先定义C:char; 然后利用Until c=‘ ’;作为终止符,将读入的字符连接存储到a[i].name中。
        代码为:
          Repeat read(c); a[i].name:=a[i].name+c; until c=’ ‘;
a[i].name:=copy(a[i].name,1,length(a[i].s)-1);
        这样做的好处是,后面的值可以直接用read语句读入。但是最后一个值后,要记得readln;

    法二〉一次读入,然后分离。
这样做需要逐个分离,对本题来说稍显复杂,但对NOIP来说此方法必须掌握,有的时候一定要用。
具体实现,读入一个字符串S。利用pos(‘ ‘,s);找出空格位置。再利用Copy函数,和Val函数进行截取,和转换。
部分代码:(s:string;j,ok:integer)
  readln(s);
  j:=pos(‘ ‘,s);
  a[i].name:=copy(s,1,j-1);
  s:= copy(s,j+1,50); //当长度〉字符串长度是,为后面全部截取。
  j:=pos(‘ ‘,s);
  Val(copy(s,1,j-1),a[i].qp,ok);
  s:= copy(s,j+1,50);
…..
对于符号用if语句作一下判断就是了,太easy不写了,后面还有几个值,用同样方法处理就可以了。
  以上完成了数据库的建库工作,后面是统计,当然,我们在没读完一行数据后就可进行统计。用If语句判断他是否能得到相应的分值即可。分5条If语句写,每回可以就加入相应的分值。
  将每个的分值汇总计入到总数变量ZD当中。与当前最大值进行比较,得到Max对应的I值。后面就是输出的问题了。
[小结、注意]
  本题为简单题,只要思路明确清晰,就可AC。时间复杂度O(n)。但有一个细节,ZD变量必须定义Longint或以上类型否则会Error201的。

第二题 过河-River

[问题分析]
此题初看是一个典型的搜索题。从河的一侧到河的另一侧,要找最少踩到的石头数。但从数据范围来看。1..109长度的桥。就算是O(n)的算法也不能在一秒内出解。
如果搜索石子,方法更困难。这要考虑到前面以及后面连续的石子。若换一种方法。用动态规划,以石子分阶段的一维动规,时间复杂度是O(n2)。最多也只有100×100的时间。但是这样分状态就十分复杂。因为石头的分布是没有任何规律,而且会有后效性。
这样只好有回到搜索。搜索石子会和动规一样没有规律。我们一桥的长度为对象进行搜索,然后再加上一个巧妙的剪枝就可以在很短的时间内出解。可以号称为O(m2)。[批注:号称一词已成为湖南OI本世纪流行词汇 ]

[题目实现]
先以时间为对象进行搜索。时间复杂度为O(L)。从桥的一侧到另一侧,中间最多只有100个石子。假设桥长为最大值(109),石头数也为最大值(100)。这样中间一定会有很多“空长条” (两个石子中的空地),处理时把这些跳过,就只会有M次运算。关键是找出每一个可以跳过的“空长条”。
我们可以先把青蛙可以跳出的所有可能求出,然后就可以求出可以忽略的“空长条”。


[特殊算法]
a[i]:前i个坐标中石子最小个数,初始为第i个坐标的石子个数
b[i]:第i个石子坐标
动规
a[0]=0;
对n>=t
a[n]=min{a[n]+a[n-s],a[n]+a[n-s-1], ...,a[n]+a[n-t]}
对s=<n<t
a[n]=max{a[n]+a[n-s],a[n]+a[n-s-1],...,a[n]+a[0]}
但由于n较大直接动规会超时。所以要将n压缩
查看坐标,可以发现,如果b[i]-b[i-1]>t,显然对于b[i-1]+t<n<b[i],a[n]总是等于a[b[i-1]]..a[b[i-1]+t]中的数,因此可对其进行压缩。
注意,在计算过程中,由于其中有一些坐标是永远走不到的,因此需要用一个布尔型的数组c[n]进行判断。方法是,对于c[n],如果0<n<s,则c[n]为false,如果n>s,c[n-t],c[n-t+1],...,c[n-s]都为false,则c[n]也为false。
 

第三题 篝火晚会-fire

[问题评估]

此题或许大多数人会觉得很麻烦。或许有人会选择搜索来做,显然,50000的数据量不可能允许搜索不超时。或许有人会用贪心,但是却无从下手。
动态规划?怎么划阶段更是一个难题。然而,此题却不是考察选手的算法的,而是考察你从题目中找出基本核心的能力。

[题目实现]
题目给你的初始状态是一个回路,从第一个同学前断开,不难看出这是一个严格的上升序列。而输入的数据也可以将之构成一个包含所有同学的回路,否则就达不到没个人的愿望。

我们可以用两的数组来储存两个数组的状态,初始状态为st,目标状态为en。st[i]=i,
i<=n。而输入数据我门可以先用一个二维E数组储存,E[I,1]即表示第I 个人的第一个愿望。我们将目标状态数组en的第一个元素赋值为1,然后就可以把s[1]的第一个愿望加入数组为s[2],依次我们可以逐个加入,加入没个元素的时候,还要判断一下每个元素是否在数组当中,如果在,那就取第2个愿望。如果第二个愿望也在数组当中,那么我们的目标状态的数组也就构造完成了。

如果每个人的愿望都能实现,显然,目标状态的数组的元素必定是N,而假如不是,那么就可以输出-1了。
此时,问题就显的简单些了,如何让一个数组从一中状态变成另一种状态,相信有很多方法,可还是个麻烦事。
从目标状态转换成初始状态的步数是等同于初始状态转换成目标状态,而此时再看看初始状态的数组,相信你已经看出些疑端了吧!

排序!!!

对,其实从目标状态转换成初始状态的过程就是一个排序的过程,而且还是一个最简单的冒泡排序的过程!
到了这了,问题已经明了了,题目所求就是每次进行连续交换的人数总和,这样,一个看似复杂的题目就变的异常的简单了!而题目2秒的时间限制更是保证了冒泡排序经过一些优化以及剪枝后不会超时。
但是,千万不能用其他的排序法来解决。虽然能让你的程序变的更快,却同时你也得不正确的解!

第四题 等价表达式-Equal
[问题分析]
这道题目拿到手后,一般可以想到的方法就是可不可以将所有的表达式全部转化为最简形式,这时,你就想到了一种一般的解决方案。即将所有的表达式全部化为最简,然后再计算,这种方法是一种准确的方法。但要在考场上实现,有一些麻烦,需要一些时间。这种方法的解决过程是:先将阶乘化乘,再展开。计算同时合并同类项,留下一个数组。然后比较每一个表达式的数组与题目数组是否相同,时间效率也并不高。那么怎么办呢?

[模型建立和实现]
我们这里介绍一种利用必要条件的解决方案。
即两个表达式如果等价,那么无论a为何值,两个表达式计算出的值都相等。这时,我们以不同的a值代入各式,可以快速排斥那些不同的表达式,留下的便是等价的了。
我们怎样取值呢?这里推荐几种有效的方法:
1>取随机函数生成的数列。这种方法比较有效,无规律。
2>取伪随机数列。这是一种比较便于人工控制的手段。
3>取实数。由于其他皆为整数,小数部分便成为判断的优越条件。
一般情况下取4~7组值便可通过极大部分情况,实数需要更小。如果取更多组的值,便可以通过几乎所有的情况(将两式连立,只有当取值都为方程的解时才会出现误判,显然这样的几率是极小的)。
补充:这道题可能会有选手在数据类型上选择不当,导致一些情况会出现溢出。

[表达式求值]
经过上面的叙述,难点落在了表达式求值上,在这里我们介绍一下最一般、最简单的方法,栈运算。

用栈实现表达式求值的方法:

首先,我们要给每一个符号一个优先级:

符号   + -   *   /   ^   (   )
栈内级别   2   4   6   0   8
栈外级别   1   3   5   8   0

可以看到,优先级高的符号先算。为了方便起见,我们定义特殊符号#,它级别最低(赋-1)
先将它置栈底,然后依次读入每个字符,如果是数字则入数栈。如果是符号,就与栈顶符号比较优先级。如果相等,则退栈,读下一字符。如果栈外大,则入栈。如果栈内大,则取栈顶元素与数栈最顶2元素运算,结果入数栈。这个符号继续处理(再与栈顶比较)。直到读到最后符号#使栈底#出栈时。数栈顶即为表达式结果。

由此,本题已经变得清晰了,剩下的就是具体将我们的表述变成代码。


解题报告并非源程,这样更有助于思维的锻炼,第一题中的一些代码均是在Word中编写,如有语法错误,还请谅解。谢谢!
离线canyvv
只看该作者 6 发表于: 2006-11-15
这是2005年的,c版,大家还有其他的话,麻烦顶上来
离线stevenjl

只看该作者 7 发表于: 2006-11-15
C语言???

j:=pos(‘ ‘,s);


怎么看都是Pascal
Dream Walker...
快速回复
限100 字节
 
上一个 下一个