切换到宽版
  • 8987阅读
  • 2回复

NOIP2007提高组复赛试题解析 [复制链接]

上一主题 下一主题
离线leocy002
 
只看楼主 倒序阅读 0 发表于: 2008-02-04
  1.统计数字(count.pas/c/cpp)
【问题描述】
    某次科研调查时得到了n个自然数,每个数均不超过1500000000(1.5*109)。已知不相同的数不超过10000个,现在需要统计这些自然数各自出现的次数,并按照自然数从小到大的顺序输出统计结果。
【输入】
    输入文件count.in包含n+1行;
    第一行是整数n,表示自然数的个数;
    第2~n+1每行一个自然数。
【输出】
    输出文件count.out包含m行(m为n个自然数中不相同数的个数),按照自然数从小到大的顺序输出。每行输出两个整数,分别是自然数和该数出现的次数,其间用一个空格隔开。
【输入输出样例】
count.in
8
2
4
2
4
5
100
2
100
count.out
2 3
4 2
5 1
100 2
【限制】
    40%的数据满足:1<=n<=1000
    80%的数据满足:1<=n<=50000
    100%的数据满足:1<=n<=200000,每个数均不超过1500 000 000(1.5*109)
【解题思想1】显然,此题可以用排序的方法来解决,根据n的范围,可以看出,O(nlogn)的算法是可以接受的。
【解题思想2】维护一个二叉树,以数的大小作为节点的权值,以数的重复次数作为节点的附加信息。之后中序遍历即可。 看起来,树内的节点个数应该不到n,所以可能表现不错,其算法复杂度依然为O(nlogn)
【实际数据规模】挺小的,而且也没有传说中的卡Qsort的数据,全部都很温柔。
【分析】这个题目实在不能说是一道TG组的好题。实际上,个人认为题目最大的意义在于:提供了10个测试排序算法的不怎么特别好的数据。话说回来,此题是送分题,但是送分题送的这么水,考察的也就只有OIers的细心程度了。在考试的时候,要相信有简单的题目,要相信有直接的算法。在我的身边就有几个同学因为这个题目与一等失之交臂,这是最可惜的事情。
var a:array[1..200001] of longint;
i,j,k,m,n:longint;
procedure qsort(s,t:longint);
var i,j,mid,temp:longint;
begin
i:=s;j:=t;mid:=a[(s+t) div 2];
while i<=j do
  begin
  while a<mid do inc(i);
  while a[j]>mid do dec(j);
  if i<=j then
    begin
    temp:=a;a:=a[j];a[j]:=temp;
    inc(i);dec(j);
    end;
  end;
if i<t then qsort(i,t);
if j>s then qsort(s,j);
end;
begin
readln(n);
for i:=1 to n do readln(a);
qsort(1,n);
a[n+1]:=maxlongint;
k:=1;
for i:=2 to n+1 do
  if a<>a[i-1] then
  begin writeln(a[i-1],' ',k); k:=1;end
  else k:=k+1;
end.
                                                  2.字符串的展开(expand.pas/c/cpp)
【问题描述】
    在初赛普及组的“阅读程序写结果”的问题中,我们曾给出一个字符串展开的例子:如果在输入的字符串中,含有类似于“d-h”或者“4-8”的字串,我们就把它当作一种简写,输出时,用连续递增的字母获数字串替代其中的减号,即,将上面两个子串分别输出为“defgh”和“45678”。在本题中,我们通过增加一些参数的设置,使字符串的展开更为灵活。具体约定如下:
    (1) 遇到下面的情况需要做字符串的展开:在输入的字符串中,出现了减号“-”,减号两侧同为小写字母或同为数字,且按照ASCII码的顺序,减号右边的字符严格大于左边的字符。
    (2) 参数p1:展开方式。p1=1时,对于字母子串,填充小写字母;p1=2时,对于字母子串,填充大写字母。这两种情况下数字子串的填充方式相同。p1=3时,不论是字母子串还是数字字串,都用与要填充的字母个数相同的星号“*”来填充。
    (3) 参数p2:填充字符的重复个数。p2=k表示同一个字符要连续填充k个。例如,当p2=3时,子串“d-h”应扩展为“deeefffgggh”。减号两边的字符不变。
    (4) 参数p3:是否改为逆序:p3=1表示维持原来顺序,p3=2表示采用逆序输出,注意这时候仍然不包括减号两端的字符。例如当p1=1、p2=2、p3=2时,子串“d-h”应扩展为“dggffeeh”。
    (5) 如果减号右边的字符恰好是左边字符的后继,只删除中间的减号,例如:“d-e”应输出为“de”,“3-4”应输出为“34”。如果减号右边的字符按照ASCII码的顺序小于或等于左边字符,输出时,要保留中间的减号,例如:“d-d”应输出为“d-d”,“3-1”应输出为“3-1”。
【输入】
    输入文件expand.in包括两行:
    第1行为用空格隔开的3个正整数,一次表示参数p1,p2,p3。
    第2行为一行字符串,仅由数字、小写字母和减号“-”组成。行首和行末均无空格。
【输出】
    输出文件expand.out只有一行,为展开后的字符串。
【输入输出样例1】
expand.in
1 2 1
expand.out
abcs-w1234-9s-4zz abcsttuuvvw1234556677889s-4zz
【输入输出样例2】
expand.in
2 3 2
expand.out
a-d-d aCCCBBBd-d
【输入输出样例3】
expand.in
3 4 2
expand.out
di-jkstra2-6 dijkstra2************6
【限制】
    40%的数据满足:字符串长度不超过5
    100%的数据满足:1<=p1<=3,1<=p2<=8,1<=p3<=2。字符串长度不超过100
解题思路:也是个水题,考察对语言的运用能力,字符串处理的技巧和仔细读题的能力,测试数据已经包括所有情况了,但是有些细节要想清楚,而且一些特殊的情况也要单独测试一下,如9-a-a,--a-1;
需要注意的是:首尾‘-’的情况,要用ansistring
Var p1, p2, p3, ii, jj, i, ch1, ch2: longint;
s: string;
procedure print(st, en: longint);
begin
  if p3 = 1 then
    for ii := st to en do begin
      for jj := 1 to p2 do write(chr(ii));
      end
    else
      for ii := en downto st do begin
        for jj := 1 to p2 do write(chr(ii));
      end; 
end;
procedure print0(st, en: longint);
begin
    for ii := st to en do
      for jj := 1 to p2 do write('*');
end;
procedure judge;
begin
ch1 := ord(s[i-1]);
ch2 := ord(s[i+1]);
if ch1 < ch2 then begin
  if (ch1 >= 48) and (ch1 <= 57) and (ch2 >= 48) and (ch2 <= 57) then begin
    if p1 = 3 then print0(ch1+1, ch2-1) else print(ch1+1, ch2-1);
  exit;
  end;
  if (ch1 >= 97) and (ch1 <= 122) and (ch2 >= 97) and (ch2 <= 122) then begin
    case p1 of
      1: print(ch1+1, ch2-1);
      2: print(ch1-31, ch2-33);
      3: print0(ch1+1, ch2-1);
    end;
    exit;
  end;
end;
write('-');
end;
begin
readln(p1, p2, p3);readln(s);write(s[1]);
for i := 2 to length(s)-1 do
    if s = '-' then judge
else  write(s);
writeln(s[length(s)]);
end.
                                          3.矩阵取数游戏(game.pas/c/cpp)
【问题描述】帅帅经常更同学玩一个矩阵取数游戏:对于一个给定的n*m的矩阵,矩阵中的每个元素aij据为非负整数。游戏规则如下:
    1. 每次取数时须从每行各取走一个元素,共n个。m次后取完矩阵所有的元素;
    2. 每次取走的各个元素只能是该元素所在行的行首或行尾;
    3. 每次取数都有一个得分值,为每行取数的得分之和;每行取数的得分 = 被取走的元素值*2i,其中i表示第i次取数(从1开始编号);
    4. 游戏结束总得分为m次取数得分之和。
帅帅想请你帮忙写一个程序,对于任意矩阵,可以求出取数后的最大得分。
【输入】
    输入文件game.in包括n+1行;
    第一行为两个用空格隔开的整数n和m。
    第2~n+1行为n*m矩阵,其中每行有m个用单个空格隔开
【输出】
    输出文件game.out仅包含1行,为一个整数,即输入矩阵取数后的最大的分。
【输入输出样例1】
game.in
2 3
1 2 4
3 4 2
game.out
82
【输入输出样例1解释】
    第1次:第一行取行首元素,第二行取行尾元素,本次的氛围1*21+2*21=6
    第2次:两行均取行首元素,本次得分为2*22+3*22=20
    第3次:得分为3*23+4*23=56。总得分为6+20+56=82
【输入输出样例2】
game.in
1 4
4 5 0 5
game.out
122
【输入输出样例3】
game.in
2 10
96 56 54 46 86 12 23 88 80 43
16 95 18 29 30 53 88 83 64 67
game.out
316994
【限制】
    60%的数据满足:1<=n, m<=30,答案不超过1016
    100%的数据满足:1<=n, m<=80,0<=aij<=1000
解题思路:很多人可能会被第一个样例误导认为贪心,第二个样例就提示我们贪心是错误的,其实有经验的选手一看就是dp,可以看作类似于石子归并,每次在2边进行合并我们可以把问题转化为每次要取完一整行,一共取m次。注意到每一行都是独立的一个结构,所以可以每行的结果计算出来后求和。
    对于每一行,f(i,j)表示从i到j这一子段单独操作可以达到的最大权值。
    有状态转移方程:f(i,j) = 2*max(a(i)+f(i+1,j),a(j)+f(i,j-1))
    边界f(i,i)=2*a(i)
    最后答案就把每一行的f(0,m-1)加起来即可。这里可以算得答案不超过30位10进制数,所以高精度的digit数组开到30足够了。乘法也不用写,用两次加法即可。
Const  maxn=80; one=10000;
Type  num=array[0..25] of longint;
Var  a:array[1..maxn] of longint; f:array[1..maxn,1..maxn] of num;
m,n:longint; ans:num;
procedure add(var c:num;a,b:num);
var i,x:longint;
begin
if a[0]>b[0] then c[0]:=a[0] else c[0]:=b[0];x:=0;
for i:=1 to c[0] do
  begin
  x:=a+b+x;
  c:=x mod one;
  x:=x div one;
  end;
if x>0 then begin inc(c[0]); c[c[0]]:=x; end;
end;
procedure time(var a:num;k:longint);
var i,x:longint;
begin
x:=0;
for i:=1 to a[0] do  begin x:=a*k+x; a:=x mod one; x:=x div one; end;
while x>0 do  begin inc(a[0]);a[a[0]]:=x mod one;x:=x div one;end;
end;
function compare(a,b:num):boolean;
var i:longint;
begin
if a[0]>b[0] then exit(true);
if b[0]>a[0] then exit(false);
for i:=a[0] downto 1 do
  begin
  if a>b then exit(true);
  if b>a then exit(false);
  end;
exit(false);
end;
procedure plus(var c:num;a:num;k:longint);
var i,j:longint;
begin
c:=a;i:=1; inc(c,k);
while c>=one do
  begin
  c[i+1]:=c[i+1]+c div one;
  c:=c mod one;
  inc(i);
  if c[0]<i then c[0]:=i;
  end;
end;
procedure dp;
var i,j:longint; max,temp:num;
begin
fillchar(f,sizeof(f),0);
for i:=1 to m do begin  f[i,i][0]:=1; f[i,i][1]:=a*2; end;
for i:=m-1 downto 1 do
  for j:=i+1 to m do
  begin
    plus(max,f[i+1,j],a);
    time(max,2);
    plus(temp,f[i,j-1],a[j]);
    time(temp,2);
    if compare(temp,max) then max:=temp;
    f[i,j]:=max;
  end;
add(ans,ans,f[1,m]);
end;
procedure init;
var i,j:longint;
begin
readln(n,m);
for i:=1 to n do begin for j:=1 to m do read(a[j]); readln; dp; end;
end;
procedure outs;
var i:longint;
begin
write(ans[ans[0]]);
for i:=ans[0]-1 downto 1 do
  begin
  write(ans div 1000 mod 10);
  write(ans div 100 mod 10);
  write(ans div 10 mod 10);
  write(ans mod 10);
  end;
end;
begin
init;outs;
end.
                                            4.树网的核(core.pas/c/cpp)
【问题描述】设T=(V, E, W) 是一个无圈且连通的无向图(也称为无根树),每条边到有正整数的权,我们称T为树网(treebetwork),其中V,E分别表示结点与边的集合,W表示各边长度的集合,并设T有n个结点。
路径:树网中任何两结点a,b都存在唯一的一条简单路径,用d(a, b)表示以a, b为端点的路径的长度,它是该路径上各边长度之和。我们称d(a, b)为a, b两结点间的距离。
    D(v, P)=min{d(v, u), u为路径P上的结点}。
    树网的直径:树网中最长的路径成为树网的直径。对于给定的树网T,直径不一定是唯一的,但可以证明:各直径的中点(不一定恰好是某个结点,可能在某条边的内部)是唯一的,我们称该点为树网的中心。
    偏心距ECC(F):树网T中距路径F最远的结点到路径F的距离,即
          ECC(F)=max{d(v, F),v∈V}
    任务:对于给定的树网T=(V, E, W)和非负整数s,求一个路径F,他是某直径上的一段路径(该路径两端均为树网中的结点),其长度不超过s(可以等于s),使偏心距ECC(F)最小。我们称这个路径为树网T=(V, E, W)的核(Core)。必要时,F可以退化为某个结点。一般来说,在上述定义下,核不一定只有一个,但最小偏心距是唯一的。
    下面的图给出了树网的一个实例。图中,A-B与A-C是两条直径,长度均为20。点W是树网的中心,EF边的长度为5。如果指定s=11,则树网的核为路径DEFG(也可以取为路径DEF),偏心距为8。如果指定s=0(或s=1、s=2),则树网的核为结点F,偏心距为12。
 
【输入】
    输入文件core.in包含n行:
    第1行,两个正整数n和s,中间用一个空格隔开。其中n为树网结点的个数,s为树网的核的长度的上界。设结点编号以此为1,2,……,n。
    从第2行到第n行,每行给出3个用空格隔开的正整数,依次表示每一条边的两个端点编号和长度。例如,“2 4 7”表示连接结点2与4的边的长度为7。
    所给的数据都是争取的,不必检验。
【输出】
    输出文件core.out只有一个非负整数,为指定意义下的最小偏心距。
【输入输出样例】
【输入输出样例1】
core.in
5 2
1 2 5
2 3 2
2 4 4
2 5 3
Core.out
5
【输入输出样例2】
core.in
8 6
1 3 2
2 3 2
3 4 6
4 5 3
4 6 4
4 7 2
7 8 3
core.out
5
【限制】
    40%的数据满足:5<=n<=15
    70%的数据满足:5<=n<=80
    100%的数据满足:5<=n<=300,0<=s<=1000。边长度为不超过1000的正整数
解题思路:可以证明的是,如果图中存在多条路径,则在任何一条直径上都存在一条core(反证法,用到直径的距离最大性)。因此只需讨论一条直径上的core的情况即可。接着,如果一条路径包括了一条子路径的所有边,那么子路径的解不会比父路径更优。这一点后面将用到。
    下面是算法:先寻找一条直径:任取一点u',遍历得到到它的最远点u,再对u寻找一个到它的最远点v,则路径u-v一定是一条直径(想想为什么),在刚才遍历u-v的时候记录遍历到每一个点的时候的前继,那么从v出发一直寻找前继到u,就能记录下这一条直径。这里复杂度是O(n)。
    然后枚举core:注意刚才说到了路径是越长越好,因此枚举的时候,每枚举一个初始点,向后都尽量延伸到不超过s的距离。这样每个初始点只确定一个枚举对象。对于终止点的选定可以采用一个游标的方式,当初始点从u到v扫过一遍时,游标也从u扫到了v(回想怎么求解凸多边形最远点对的),因此这里复杂度是O(n)。
    最后求ECC:暂时删除枚举对象(那条路径)上的所有边,然后以那条路径上的那些顶点作为源点开始遍历图,找到的最大距离就是该路径的ECC。这里复杂度也将是O(n)。
    因此总复杂度为O(n)+O(n)*O(n) = O(n^2),ac。
线性算法:
    同样只要考虑一条直径。先对于直径上的顶点赋值:与该顶点连通的最远点的距离。这样可以构成一个线性模型,点(记为v)有一个值(记为f),边有一个值(即原来图中v和v[i+1]之间的距离,记为e)。这一步可以O(n)完成。
    接着考虑这个线性模型。枚举过程还是和刚才一样。假设枚举的是v[a]到v这一段路径,那么,这一段路径的ECC应当是以下几个值的最大值:1)max(v[a],v[a+1]...,v); 2)max(v+e+e[i+1]+...+e[a-1],i<a); 3)max(e+e[b+1]+...+v,i>b);
如果以上3个值能够在O(1)内回答,那么总复杂度就将是O(n)
对于1,显然这是个RMQ模型,有一个O(n)算法(当然st的O(nlogn)也可以接受)。或者考虑到这个问题的特殊性,所有(a,b)类的询问区间都不是严格包含的(即任取询问(a,b)(c,d)都不存在a>b&&c<d),单调队列也是可以采用的(而且常数小,推荐使用)
    对于2,设一个数组a[1..n],a[k]表示max(v+e+...+e[k-1],i<k),那么a[k+1]=max(a[k],v[k])+e[k],因此整个a数组可以在O(n)时间内预处理得到,随后在枚举的时候直接查表即可。
    3和2同理,不再详述
Const maxn = 65536;
Var n, s, i, j, aa, bb, k, temp, st, en, ans, tou, max, len, dep, now: longint;
w, f, mid: array[0..300, 0..300] of longint;
p: array[0..50000, 1..2] of integer;
a: array[0..300] of integer;
d: array[0..300] of longint;
begin
readln(n, s);
for i := 1 to n do
begin
      w[i, i] := 0;f[i, i] := 0;
      for j := i+1 to n do
begin
        f[i, j] := maxn; f[j, i] := maxn;
        w[i, j] := maxn;w[j, i] := maxn;
end;
end;
for i := 2 to n do
begin
    read(aa, bb);
readln(w[aa, bb]);
w[bb, aa] := w[aa, bb];
f[aa, bb] := w[aa, bb];
f[bb, aa] := w[aa, bb];
end;
for k := 1 to n do
    for i := 1 to n do
      for j := 1 to n do
      if f[i, j] > f[i, k] + f[k, j] then f[i, j] := f[i, k] + f[k, j];
fillchar(p, sizeof(p), 0);
tou := 0;max := 0;
for i := 1 to n-1 do begin
    for j := i+1 to n do begin
  if f[i, j] > max then
begin
    p[1, 1] := i; p[1, 2] := j;
    tou := 1; max := f[i, j];
    end
else begin
      if f[i, j] = max then
begin inc(tou); p[tou, 1] := i;p[tou, 2] := j;end;
    end;
end;
end;
ans := maxn;
for dep := 1 to tou do
begin
    len := 0;st := p[dep, 1];en := p[dep, 2];
now := en;
while f[now, st] <> w[now, st] do
begin
        for k := 1 to n do
begin
            if (f[st, now] = f[st, k] + w[k, now]) and (k <> now) then
begin
                inc(len);
                a[len] := now; now := k; break;
              end;
          end;
end;
inc(len);a[len] := now;inc(len);a[len] := st;
for i := 1 to len do
begin
      temp := 0;st := a;
      for k := 1 to n do  d[k] := f[st, k];
  for j := i+1 to len do
begin
      en := a[j];
      if f[st, en] > s then break;
      for k := 1 to n do
        if d[k] > f[k, en] then d[k] := f[k, en];
  end;
  for k := n downto 1 do
    if d[k] > temp then
begin
      temp := d[k];
      if temp >= ans then break;
    end;
  if temp < ans then ans := temp;
end;
end;
writeln(ans);
end.
离线yzasdf
只看该作者 1 发表于: 2008-02-05
ddddddddddd
离线indigo
只看该作者 2 发表于: 2008-09-25
谢谢,急需
快速回复
限100 字节
 
上一个 下一个