信息学奥赛一本通(C++版) 第二部分 基础算法 第三章 递推算法

信息学奥赛一本通(C++版) 第二部分 基础算法 第三章 递推算法

http://ybt.ssoier.cn:8088/

以下题目不适合初学者2018-5-17
1313 【例3.5】位数问题
1314 【例3.6】过河卒(Noip2002)
1191 流感传染
1192 放苹果
1195 判断整除
1197 山区建小学
以上题目不适合初学者2018-5-17

//1312 【例3.4】昆虫繁殖
//想到过要设两个数组,苦于过懒,没有下笔,
//从文字角度,卵长成成虫后的第一个月不产卵(过X个月产卵) 比较晦涩,还是看代码更清晰。
//http://blog.csdn.net/cynthia_wjyi/article/details/47257425此文写得很不错
//题解:
//是一道很经典的递推题目。
//用a[i]表示第i个月拥有的成虫数目,b[i]表示第i个月产生的新增卵。
//由题目可知,前x个月成虫数量始终为1,新增卵为0。
//而以后的第i个月,a[i]=a[i-1]+b[i-2],即第i个月的成虫等于第i-1个月的成虫数加上第i-2个月的新增卵(因为卵两个月后变为成虫)。而b[i]=a[i-x]*y,即第i-x月的成虫在x个月后产下y个卵。
//最后,因为是过了z个月,答案输出a[z+1]即可。
//该题语意歧义多多。
//提交,未通过,测试点2错误,看了他人代码,发现int 改成 long long
//修改,提交,AC
#include <stdio.h>
long long a[60],b[60];//原来int a[60],b[60];
int main(){
    int x,y,z,i;
    scanf("%d%d%d",&x,&y,&z);
    for(i=1;i<=x;i++)a[i]=1,b[i]=0;
    for(i=x+1;i<=z+1;i++){
        b[i]=a[i-x]*y;
        a[i]=a[i-1]+b[i-2];
    }
    printf("%lld",a[z+1]);//原来 printf("%d",a[z+1]);
    return 0;
}

//1313 【例3.5】位数问题
//样例解释:
//10-99共 90个数 其中奇数个3的数是 30 31 32 34 35 36 37 38 39
//13 23 43 53 63 73 83 93
//共17个数,故偶数个数是90-17=73个
//http://www.cnblogs.com/lfyzoi/p/6875882.html?utm_source=itdadao&utm_medium=referral此文介绍得不错,摘抄如下:
//分析
//已知N的最大值为1000,1000位的数字的个数约有101000个,这是个天文数字,故不可能使用枚举法一一统计。本题需使用递推的思想:
//设E(n)为n位数中有偶数个3的数字个数,O(n)为n位数中有奇数个3的数字个数。从n位数转变为n+1位数可以在n位数的基础上增加一位:
//偶数的英文为“Even”, 奇数的英文为“Odd”,在这里“O(n)”可能会和时间复杂度的概念混淆,小心避免理解出错。
//如果n位数中有偶数个3,新增一个数位为3,则n+1位数中有奇数个3
//如果n位数中有偶数个3,新增一个数位为非3,则n+1位数中有偶数个3
//如果n位数中有奇数个3,新增一个数位为3,则n+1位数中有偶数个3
//如果n位数中有奇数个3,新增一个数位为非3,则n+1位数中有奇数个3
//递推关系用公式表达如下:
//E(n+1)=E(n)*9+O(n) => E(n)=E(n-1)*9+O(n-1)
//O(n+1)=E(n)+O(n)*9 => O(n)=E(n-1)+O(n-1)*9
//1位数有10个:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 数字3中有奇数个(1个)数字3,其它9个数字中有偶数个(0个)数字3,因此E(1)=9, O(1)=1,这就是递推的已知条件,也是我们递推的起点。但在递推的时候,数字的第一位是不能为0的,所以递推到第一位(我们从低位往高位推)时,不是乘9,而是乘8。
//当N很大时,数字必定会超过int能够表达的范围从而导致溢出,所以在递归过程中要不断的按照题目要求和12345求余以将数字控制在一定范围内。
//通过递推计算,时间复杂度为O(N)。
//数据范围:输入一个数N(1<=N<=1000)
#include <stdio.h>
int E[1010],O[1010];
int main(){
    int n,i,x;
    E[1]=9,O[1]=1,x=9;
    scanf("%d",&n);
    for(i=2;i<=n;i++){
        if(i==n)x=8;//最高位,0不能做最高位,故为8
        E[i]=(E[i-1]*x+O[i-1])%12345;//E[i-1]*9 0 1 2 4 5 6 7 8 9 O[i-1] 3
        O[i]=(O[i-1]*x+E[i-1])%12345;
    }
    printf("%d",E[n]);
    return 0;
}


//NOIP 2002 普及组 复赛 过河卒
//1314 【例3.6】过河卒(Noip2002)
//洛谷 P1002 过河卒
//样例每通过,仔细看题,发现起点是从(0,0)开始,而误以为是(1,1)开始,修改代码
//样例通过,提交,测试点3,4WA,重新读题,发现没说A!=B
//处理了A==B的情况,提交,测试点3,4WA,
//输入:
//4 8 2 4
//输出:
//0
//是一组好的测试数据,
//输入测试数据,跟踪程序,发现问题所在,如下图,是错误数据

//下图是正确数据

//边界上,若有马管辖的范围,那么之后的数据可能都是0,所谓一夫当关,万夫莫开。
//马上就行修改,提交AC。
//不借助测试数据,这个错误极难发现。
//2017-10-29 19:39
#include <stdio.h>
#include <string.h>
long long a[30][30];
int vis[30][30];
int next[][2]={{2,1},{1,2},{-1,2},{-2,1},{-2,-1},{-1,-2},{1,-2},{2,-1}};
int main(){
    int n,m,x,y,i,j,nx,ny;
    memset(vis,0,sizeof(vis));
    scanf("%d%d%d%d",&n,&m,&x,&y);
    a[0][0]=0;//此处写成vis[0][0]=1,a[0][0]=0;//处理A==B的情况
    vis[x][y]=1,a[x][y]=0;//设置马管辖的位置
    for(i=0;i<8;i++){
        nx=x+next[i][0],ny=y+next[i][1];
        if(0<=nx&&nx<=n&&0<=ny&&ny<=m){
            vis[nx][ny]=1,a[nx][ny]=0;
        }
    }
    for(i=0;i<=n;i++)
        if(vis[i][0]==1)while(i<=n){i++,a[i][0]=0;}
        else a[i][0]=1;//for(i=0;i<=n;i++)a[i][0]=1;//此处写成 for(i=0;i<=n;i++)a[i][1]=1;
    for(j=0;j<=m;j++)
        if(vis[0][j]==1)while(j<=m){j++,a[0][j]=0;}
        else a[0][j]=1;//for(j=0;j<=m;j++)a[0][j]=1;//此处写成 for(j=0;j<=m;j++)a[1][j]=1;
    for(i=1;i<=n;i++)
        for(j=1;j<=m;j++)
            if(vis[i][j]==0)
                a[i][j]=a[i][j-1]+a[i-1][j];
    printf("%lld\n",a[n][m]);
    return 0;
}

//1188 菲波那契数列(2)
//有一个疑问,递归调用层数过多导致溢出,如何计算层数,或者如何评估层数。
#include <stdio.h>
int a[1000100];
int main(){
    int n,x,i;
    a[1]=1,a[2]=1;
    for(i=3;i<=1000000;i++)
        a[i]=(a[i-1]+a[i-2])%1000;
    scanf("%d",&n);
    for(i=1;i<=n;i++){
        scanf("%d",&x);
        printf("%d\n",a[x]);
    }
    return 0;
}
//1189 Pell数列
#include <stdio.h>
int a[1000100];
int main(){
    int n,k,i;
    a[1]=1,a[2]=2;
    for(i=3;i<=1000000;i++)
        a[i]=(2*a[i-1]+a[i-2])%32767;
    scanf("%d",&n);
    for(i=1;i<=n;i++){
        scanf("%d",&k);
        printf("%d\n",a[k]);
    }
    return 0;
}


//1190 上台阶
//提交,未通过,全部运行超时,
//在想,递归是不能用了,用循环迭代来处理。
//测试过程中,int 不够用 ,改成long long
//将 long long改成int,提交AC
//该题测试数据明显有误,int 在n比较大时,溢出。
#include <stdio.h>
int a[110];
void f(){
    int i;
    a[1]=1,a[2]=2,a[3]=4;
    for(i=4;i<=99;i++)
        a[i]=a[i-1]+a[i-2]+a[i-3];
}
int main(){
    int n;
    f();
    while(scanf("%d",&n)&&n)printf("%d\n",a[n]);
    return 0;
}

//1191 流感传染
//第一直觉,广度优先遍历
#include <stdio.h>
char s[110];
int a[110][110];
struct node{
    int r,c,d;//r行 c列 d天  
}q[10100];
int next[][2]={{-1,0},{1,0},{0,-1},{0,1}};
int main(){
    int n,m,i,j,h,t,r,c,d,nr,nc,cnt=0;
    h=t=1;
    scanf("%d",&n);
    for(i=0;i<n;i++){
        scanf("%s",s);
        for(j=0;j<n;j++)
            if(s[j]=='.')a[i][j]=0;
            else if(s[j]=='@')a[i][j]=1,q[t].r=i,q[t].c=j,q[t].d=1,t++;
            else if(s[j]=='#')a[i][j]=2;
    }
    scanf("%d",&m);
    while(h<t){
        r=q[h].r,c=q[h].c,d=q[h].d;
        if(d==m)break;
        for(i=0;i<4;i++){
            nr=r+next[i][0],nc=c+next[i][1];
            if(a[nr][nc]==0&&0<=nr&&nr<n&&0<=nc&&nc<n)
                q[t].r=nr,q[t].c=nc,q[t].d=d+1,t++,a[nr][nc]=1;
        }
        h++;
    }
    for(i=0;i<n;i++)
        for(j=0;j<n;j++)
            if(a[i][j]==1)cnt++;
    printf("%d",cnt);
    return 0;
}

//1192 放苹果
//http://www.cnblogs.com/dongsheng/archive/2012/08/15/2640468.html此文介绍得不错,摘抄如下:
//8    解题分析:
//9         设f(m,n) 为m个苹果,n个盘子的放法数目,则先对n作讨论,
//10         当n>m:必定有n-m个盘子永远空着,去掉它们对摆放苹果方法数目不产生影响。即if(n>m) f(m,n) = f(m,m)  
//11         当n<=m:不同的放法可以分成两类:
//12         1、有至少一个盘子空着,即相当于f(m,n) = f(m,n-1);  
//13         2、所有盘子都有苹果,相当于可以从每个盘子中拿掉一个苹果,不影响不同放法的数目,即f(m,n) = f(m-n,n).
//14         而总的放苹果的放法数目等于两者的和,即 f(m,n) =f(m,n-1)+f(m-n,n)
//15     递归出口条件说明:
//16         当n=1时,所有苹果都必须放在一个盘子里,所以返回1;
//17         当没有苹果可放时,定义为1种放法;
//18         递归的两条路,第一条n会逐渐减少,终会到达出口n==1;
//19         第二条m会逐渐减少,因为n>m时,我们会return f(m,m) 所以终会到达出口m==0.
//该题可以放在阅读程序写结果
//递推
#include <stdio.h>
int a[20][20];
int f(int m,int n){
    int i,j;
    for(i=1;i<=n;i++)a[0][i]=1;//0个苹果
    for(i=1;i<=m;i++)a[i][1]=1;//1个盘子
    for(i=1;i<=m;i++)//此处写成 for(i=1;i<=n;i++)
        for(j=2;j<=n;j++)//此处写成 for(j=2;j<=m;j++)
            if(i<j)a[i][j]=a[i][i];
            else a[i][j]=a[i][j-1]+a[i-j][j];
}
int main(){
    int m,n,i,j,k;
    scanf("%d",&k);
    for(i=1;i<=k;i++){
        scanf("%d%d",&m,&n);
        f(m,n);
        printf("%d\n",a[m][n]);
    }
    return 0;
}  


//1193 吃糖果
//比1190 上台阶 简单
#include <stdio.h>
int a[30];
int main(){
    int n,i;
    a[1]=1,a[2]=1+a[2-1];
    for(i=3;i<20;i++)
        a[i]=a[i-1]+a[i-2];
    scanf("%d",&n);
    printf("%d",a[n]);
    return 0;
}
//1194 移动路线
//此文介绍得不错http://blog.csdn.net/c18854805113/article/details/70243479摘抄如下:
//蚂蚁只能向上或向右,所以最左端一列的路线是1,同理,最下一列也是1,首先赋给这两咧值,到达(m,n)点的路线与(m-1,n)和(m,n-1)有关,也就是
//a[i][j]=a[i-1][j]+a[i][j-1]
//a[m][n]就是所求的最优解。  
#include <stdio.h>
int a[30][30];
int main(){
    int m,n,i,j;
    scanf("%d%d",&m,&n);
    for(i=1;i<=m;i++)a[i][1]=1;
    for(j=1;j<=n;j++)a[1][j]=1;
    for(i=2;i<=m;i++)
        for(j=2;j<=n;j++)
            a[i][j]=a[i-1][j]+a[i][j-1];
    printf("%d",a[m][n]);
    return 0;
}
//1195 判断整除
//可以用背包问题来解决
//f[i][j]选了前i个数,余数是j的结果,1表示正确,0表示错误
//提交,未通过,测试点2,7,8,9,12,15,17,18错误
//修改,提交AC
//突然间明白了,j-a[i]的绝对值可能是一个比k大,这个没考虑到,马上再次进行修改
//f[i-1][(j-a[i]+k)%k]改成f[i-1][(j-a[i]%k+k)%k]; 再次提交,还是AC 2017-10-31 22:06
#include <stdio.h>
#include <string.h>
int a[10100],f[10100][110];
int main(){
    int n,k,i,j;
    memset(f,0,sizeof(f));
    scanf("%d%d",&n,&k);
    for(i=1;i<=n;i++)
        scanf("%d",&a[i]);
    f[0][0]=1;
    for(i=1;i<=n;i++)
        for(j=0;j<k;j++)
            f[i][j]=f[i-1][(j+a[i])%k]||f[i-1][(j-a[i]%k+k)%k];//此处写成f[i][j]=f[i-1][(j+a[i])%k]||f[i-1][(j-a[i]+k)%k];
    if(f[n][0]==0)printf("NO");
    else printf("YES");
    return 0;
}


//1196 踩方格
//http://www.cnblogs.com/konjak/p/5936888.html此文介绍得真不错,摘抄如下:
//解法: f[i]表示走 i 格的方案数。
//状态转移方程推导如下——
//设l[i],r[i],u[i]分别为第 i 步向西、东、北的方案数,f[i]为总方案数。
//l[i]=l[i-1]+u[i-1], r[i]=r[i-1]+u[i-1], u[i]=l[i-1]+r[i-1]+u[i-1]
//f[i]=l[i]+r[i]+u[i]
//    =2*l[i-1]+2*r[i-1]+3*u[i-1]
//    =2*f[i-1]+u[i-1]
//    =2*f[i-1]+f[i-2]
#include <stdio.h>
int f[30];
int main(){
    int n,i;
    scanf("%d",&n);
    f[1]=3,f[2]=7;
    for(i=3;i<=n;i++)
        f[i]=2*f[i-1]+f[i-2];
    printf("%d",f[n]);
    return 0;
}

//1197 山区建小学
//此文代码写得够短http://blog.csdn.net/baidu_38496325/article/details/74370400
//提交,未通过,测试点2,3,5答案错误
//无奈,只好再找一篇进行学习研究,http://blog.csdn.net/wanglinlin_bfcx/article/details/78006277这篇代码也够短
//提交AC,接下来好好研究,
//http://blog.csdn.net/Loi_imcy/article/details/52681991此文原理介绍得比较好,摘抄如下:
//定义 f[i][j] 为前 i 个村庄建 j 个小学,前 i 个村庄到最近学校距离之和的最小值。
//在计算 f[i][j] 时,f[k][j-1] ( k < i ) 已经被计算出来了,f[i][j] 可以由这样一种情况转移过来 : 
//在前 k 个村庄建 j - 1 所小学, 在后 k + 1 到 i 的村庄建 1 所小学 。 
//那么得出这样一个转移方程,f[i][j] = min( f[k][j-1] + cut[k+1][i] , f[i][j] );
//其中 cut[k+1][i] 代表在 k + 1 到 i 号村庄建一座小学 ,k + 1 到 i 号村庄到最近小学距离之和的最小值。
//然后就是 cut 数组的处理 , 其实在 i 村庄与 j 村庄之间建一所小学使 i 到 j 各村庄到小学距离之和最近的话,
//只要建在 (i + j) / 2 的位置上就可以了 , 感觉这个地方挺难发现的,其他的就好处理了。
//http://blog.csdn.net/loi_lxt/article/details/65937959?locationNum=9&fps=1此文介绍中点距离最短,并给出了证明
//AC 该题为该章节最难问题2017-11-6 21:58  
#include <stdio.h>
#include <string.h>
#define INF 999999999
int dis[510],b[510][510],f[510][510]; //注意f[i][j]中 i>=j 
int min(int a,int b){
    return a>b?b:a;
}
int main(){
    int m,n,i,j,k;
    scanf("%d%d",&m,&n);
    dis[1]=0;
    for(i=2;i<=m;i++){
        scanf("%d",&dis[i]);
        dis[i]+=dis[i-1];//dis[i] i村到1村的距离 
    }
    memset(b,0,sizeof(b));
    for(i=1;i<=m;i++)
        for(j=i+1;j<=m;j++)
            b[i][j]=b[i][j-1]+dis[j]-dis[(i+j)/2];//学校建在i村j村的中间村落 //b[i][j] i村到j村建1所学校,村到学校的最小距离
    for(i=1;i<=m;i++)
        for(j=1;j<=i&&j<=n;j++) 
            f[i][j]=INF;
    for(i=1;i<=m;i++)
        f[i][1]=b[1][i];//f[i][j]前i个村建j所学校,最短距离。
    for(i=2;i<=m;i++)//此处写成 for(i=1;i<=m;i++)
        for(j=2;j<=i&&j<=n;j++)//此处写成 for(j=1;j<=i&&j<=n;j++)
            for(k=j-1;k<i;k++)
                f[i][j]=min(f[i][j],f[k][j-1]+b[k+1][i]); 
    printf("%d",f[m][n]);
    return 0;
}

2017-11-6 21:58 AC该章节内容

猜你喜欢

转载自blog.csdn.net/mrcrack/article/details/78380611