kon3155 发表于 2009-2-25 10:51:24

经典算法普及——基础篇

基础但很经典的算法集中介绍(本帖部分题目及解答来自信息学竞赛网站)

1.排序算法

选择排序
快速排序

2.高精度运算

存储方法
加法运算
减法运算
乘法运算
扩大进制数
习题与练习

3.搜索算法

枚举算法
深度优先搜索
广度优先搜索
8数码问题
n皇后问题

4.搜索算法习题

枚举法习题
聪明的打字员
量水问题
染色问题
跳马问题
算24点

5.图论算法

最小生成树算法(Prim算法)
单源最短路径算法(Dijkstra算法)
任意结点最短路径算法(Floyd算法)
求有向带权图的所有环
Bellman-Ford算法
计算图的连通性
计算最佳连通分支
计算拓扑序列

6.图论算法习题

网络建设问题
最短变换问题
挖地雷
乌托邦城市
乌托邦交通中心

7.动态规划

最短路径问题
动态规划概念
骑士游历问题
最长递增子序列
合唱队形
石子合并问题
能量项链
0/1背包问题
开心的金明
金明的预算方案
加分二叉树
字串编辑距离
花瓶插花
凸多边形三角划分
快餐店

kon3155 发表于 2009-2-25 10:52:16

选择排序是一种简单而有效的排序算法,在问题规模不是很大的情况下就大胆的使用这个算法吧。
   算法主过程如下:   PROCEDURE selectsort;
      VAR
      i,j,k,temp:integer;
      BEGIN
      FOR i:=1 to n-1 DO
          BEGIN
            k:=i;
            FOR j:=i+1 to n DO
            IF a>a
               THEN k:=j;
         IF k<>i
             THEN BEGIN
                  temp:=a;
                  a:=a;
                  a:=temp;
                  END;
          END;
       END;

[ 本帖最后由 kon3155 于 2009-2-25 10:56 编辑 ]

kon3155 发表于 2009-2-25 10:55:39

快速排序是基于分治排序算法,在数据规模很大的情况下一般使用该算法。
   算法主过程如下:    procedure qsort(L,R:longint);
       var
         i,j,mid,temp:longint;
       begin
         i:=L;
         j:=R;
         mid:=a;   {随机选择一个数组中的数作为对比数}
         repeat
            while a< mid do inc(i);    {在左半部分寻找比中间数大的数}
            while mid< a do dec(j);    {在右半部分寻找比中间数小的数}
            if i< =j then   {若找到一组与排序目标不一致的数对则交换它们}
               begin
                   temp:=a;
                   a):=a;
                   a:=temp;
                   inc(i);dec(j); {继续找}
               end;
            until i >j;
            if L< j then qsort(L,j);   {若未到两个数的边界,则递归搜索左右区间}
            if i< R then qsort(i,R);
      end;注意:主程序中必须加randomize语句。

kon3155 发表于 2009-2-25 10:58:15

由于待处理的数据超过了任何一种数据类型所能容纳的范围,因此必须采用数串形式输入,并将其转化为数组。该数组的每一个元素对应一个十进制数,由其下标顺序指明位序号。由于高精度运算可能使得数据长度发生变化,因此除要用整数数组存储数据外,还需要一个整数变量纪录整数数组的元素个数,即数据的实际长度。type
numtype=array of byte;
var
a:numtype;
la:byte;
s:string;
begin
readln(s);
la:=length(s);
for i:=1 to la do
    a:=ord(s)-ord('0');
end.

kon3155 发表于 2009-2-25 10:58:50

高精度加法运算

    首先,确定a和b中的最大位数x,然后依照由低位至高位的顺序进行加法运算。在每一次运算中,a当前位加b当前位的和除以10,其整商即为进位,其余数即为和的当前位。在进行了x位的加法后,若最高位有进位(a<>0),则a的长度为x+1。
以下只列出关键程序:type
numtype=array of byte;
var
a,b:numtype;
la,lb:byte;
procedure plus(var a:numtype;var la:byte;b:numtype);{利用过程实现}
var
    i,x:byte;
begin
    if la>=lb
       then x:=la
       else x:=lb;
    for i:=1 to x do
      begin
      a:=a+b;
      a:=a+a div 10;
      a:=a mod 10;
      end;
   while a<>0 do
      x:=x+1;   
   la:=x;             {最高位若有进位,则长度增加}
end;

kon3155 发表于 2009-2-25 10:59:12

高精度减法运算(a>b)

    依照由低位至高位的顺序进行减法运算。在每一次位运算中,若出现不够减的情况,则向高位借位。在进行了la位的减法后,若最高位为零,则a的长度减1。
以下只列出关键程序:type
numtype=array of byte;
var
a,b:numtype;
la,lb:byte;
procedure minus(var a:numtype;var la:byte;b:numtype);
var
    i:byte;
begin
    for i:=1 to la do
      begin
      if a<b
         then begin
                  dec(a);
                  a:=a+10;
                end;
      a:=a-b;
      end;
    while (a=0) and (la>1) do
       dec(la);
end;

kon3155 发表于 2009-2-25 10:59:39

高精度乘法运算

    按照乘法规则,从a的第1位开始逐位与c(c为字节型)相乘。在第i位乘法运算中,a的i位与c的乘积必须加上i-1位的进位,然后规整积的i-1位。
以下只列出关键程序:type
numtype=array of word;
var
a,b:numtype;
la,lb:byte;
procedure multiply(var a:numtype;c:byte);
var
    i:byte;
begin
    a:=a*c;
    for i:=2 to la do
      begin
      a:=a*c;
      a:=a+a div 10;
      a:=a mod 10;
      end;
    while a>=10 do
      begin
       inc(la);
       a:=a div 10;
       a:=a mod 10;
      end;
end;

kon3155 发表于 2009-2-25 11:01:16

扩大进制数改善高精度运算效率

    用整数数组每一个元素表示一个十进制整数的方法存在的缺点是:如果十进制的位数很多,则对应的数组的长度会很长,并增加了高精度计算的时间。
    如果用一个元素记录2位数字、3位数字或更多位数字,则数组的长度可以明显缩小,但是还要考虑数的取值范围问题,必须保证程序运行过程中不越界。在权衡了两方面的情况后得出:用一个longint记录4位数字是最佳的方案。那么这个数组就相当于一个10000进制的数,其中每一个元素都是10000进制下的一位数。
一、数据类型定义:type
numtype=array of longint;{可以存储40000位十进制数}
var
a,n:numtype;
la,ln:byte;
s:ansistring;       {任意长度的字符串类型}二、整数数组的建立和输出readln(s);
k:=length(s);
for i:=1 to k do
begin
j:=(k-i+4) div 4;
n:=n*10+ord(s)-48;
end;
ln:=(k+3) div 4;

当得出最后结果n后,必须按照次高位到最低位的顺序,将每一位元素由10000进制数转换成十进制数,即必须保证每个元素对应4位十进制数。例如n=0015(0<=i<=ln-2),对应的十进制数不能为15,否则会导致错误结果。可以按照如下方法输出n对应的十进制数:write(n);
for i:=ln-1 downto 1 do
write(n div 1000,(n div 100) mod 10,(n div 10) mod 10,n mod 10);

三、基本运算
    两个10000进制整数的加法和减法与前面的十进制运算方法类似,只是进制变成了10000进制。
    1、整数数组减1(n:=n-1,n为整数数组)
    从n出发寻找第一个非零的元素,由于接受了低位的借位,因此减1,其后缀全为9999。如果最高位为0,则n的长度减1。
    j:=1;
    while (n=0) do inc(j);             {寻找第一个非零的元素}
    dec(n);                            {该位接受低位的借位,因此减1}
    for i:=1 to j-1 do
       n:=9999;                        {其后缀全为9999}
    if (j=ln) and (n=0)                {如果最高位为0,则n的长度减1}
       then dec(ln);

2、整数数组除以整数(a:=a div i,a为整数数组,i为整数)
    按照从高位到低位的顺序,逐位相除,把余数乘进制后加到下一位继续相除。如果最高位为0,则a的长度减1。
l:=0;
    for j:=la downto 1 do
      begin
      inc(a,l*10000);
      l:=a mod i;
      a:=a div i;
      end;
    while a=0 do dec(la);3、两个整数数组相乘(a:=a*n,a和n为整数数组)
    按照从高位到低位的顺序,将数组a的每一个元素与n相乘。procedure multiply(a,b:numtype;la,lb:longint;var s:numtype;var ls:longint);
var
    i,j:longint;
begin
    for i:=1 to la do
      for j:=1 to lb do
          s:=s+a*b;
    for i:=1 to la+lb-1 do
      begin
      s:=s+s div 10000;
      s:=s mod 10000;
      end;
    if s=0
       then ls:=la+lb-1
       else ls:=la+lb;
end;


[ 本帖最后由 kon3155 于 2009-2-25 13:11 编辑 ]

kon3155 发表于 2009-2-25 11:02:38

习题与练习

一、用高精度计算出s=1!+2!+3!+...+100!。
    参考答案

二、两个高精度数相乘。
    参考答案

三、2k进制数(digital.pas)(NIOP2006第四题)
【问题描述】设r是个2k 进制数,并满足以下条件:
   (1)r至少是个2位的2k 进制数。
   (2)作为2k 进制数,除最后一位外,r的每一位严格小于它右边相邻的那一位。
   (3)将r转换为2进制数q后,则q的总位数不超过w。
    在这里,正整数k(1≤k≤9)和w(k<W≤30000)是事先给定的。
    问:满足上述条件的不同的r共有多少个?
    我们再从另一角度作些解释:设S是长度为w 的01字符串(即字符串S由w个“0”或“1”组成),S对应于上述条件(3)中的q。将S从右起划分为若干个长度为k 的段,每段对应一位2k进制的数,如果S至少可分成2段,则S所对应的二进制数又可以转换为上述的2k 进制数r。
    例:设k=3,w=7。则r是个八进制数(23=8)。由于w=7,长度为7的01字符串按3位一段分,可分为3段(即1,3,3,左边第一段只有一个二进制位),则满足条件的八进制数有:
    2位数:高位为1:6个(即12,13,14,15,16,17),高位为2:5个,…,高位为6:1个(即67)。共6+5+…+1=21个。
    3位数:高位只能是1,第2位为2:5个(即123,124,125,126,127),第2位为3:4个,…,第2位为6:1个(即167)。共5+4+…+1=15个。
    所以,满足要求的r共有36个。
【输入文件】
    输入文件digital.in只有1行,为两个正整数,用一个空格隔开:
k W
【输出文件】
    输出文件digital.out为1行,是一个正整数,为所求的计算结果,即满足条件的不同的r的个数(用十进制数表示),要求最高位不得为0,各数字之间不得插入数字以外的其他字符(例如空格、换行符、逗号等)。
(提示:作为结果的正整数可能很大,但不会超过200位)
【输入样例】
3 7
【输出样例】
36

【题目分析】
    考虑一个首位为r、位数为n且除最后一位每一位严格小于右边的2k进制数,它的种数等于从2k-(r+1)个自然数中选出n-1个升序排列。
    而题目所求答案等于首位为1..2w mod k-1、位数为w/k+1且符合要求的2k进制数种数,加上首位任意、位数不超过w/k不低于2的且符合要求的2k进制数种数。(当w mod k=0时直接考虑后者即可)
    因为k,W是指定的,则可以确定2k进制数r的最长位数是w div k +1,设d=w div k,则d位2k进制数组成的2位以上d位以下的升序排列数应该是:
http://www.zjtg.cn/itjs/suanfa/images/6_6.gif
这里还要比较d与的2k-1大小,必须保证d<=2k-1。
    最后考虑首位的情况,显然首位的最大二进制位数是w mod k,则最大值m=2w mod k- 1,则首位不大于m,总位数是d+1位的升序排列数应是:http://www.zjtg.cn/itjs/suanfa/images/6_6_1.gif
      
这里也要比较d与的2k-1大小,必须保证d<=2k-1-m。
         两者相加即所求,因为最终的运算结果可能会很大,所以必须使用高精度运算。
【参考程序】
【NIOP满分程序】

[ 本帖最后由 kon3155 于 2009-2-25 11:27 编辑 ]

kon3155 发表于 2009-2-25 11:03:05

枚举法(通常也称穷举法)是指在一个有穷的可能的解的集合中,枚举出集合中的每一个元素,用题目给定的约束条件去判断其是否符合条件,若满足条件,则该元素即为整个问题的解;否则就不是问题的解。
   【枚举算法解题必须满足下列条件】
    ⑴ 可预先确定解元素的个数n,且问题的规模不是很大;
    ⑵ 对于每个解变量A1,A2,…An的可能值必须为一个连续的值域。
    【枚举算法实现】
    通常使用嵌套的FOR结构循环语句枚举每个变量的取值,在最里层的循环中判断是否满足给定的条件,若满足则输出一个解。
    【枚举算法优化】
    ⑴ 减少枚举的变量。
   在使用枚举法之前,先考虑一下解元素之间的关联,将那些能由已枚举解元素推算出来的变量直接用计算表达式代替。
    ⑵ 减少枚举变量的值域。
    通过各枚举变量间的数学关系定义解元素的值域,在保证不遗漏的情况下越小越好。
    ⑶ 分解约束条件。
    将拆分的约束条件嵌套在相应的循环体间,即尽可能根据约束条件减少变量个数和缩小值域。
页: [1] 2 3 4 5 6 7 8 9
查看完整版本: 经典算法普及——基础篇