nenuacm 2019新生训练#4 题解

A - Sum Problem HDU 1001

这道题就是简单地求一下1到N的和,我们可以模拟一下,一个for循环,把从1到N的数一个个加起来。注意一下输出格式,每个答案后面还要加一个换行。

递归的话也可以(但没必要= =)。

#include <cstdio>
#include <cstdlib>

using namespace std;

int main()
{
    int i,n;
    while(scanf("%d",&n)!=EOF)
    {
        int sum=0;
        for (i=1;i<=n;i++)
            sum+=i;
        printf("%d\n",sum);
        printf("\n");
    }
}

直接用等差数列求和公式

这里注意一下,直接用int存的话会炸,虽然答案说了最终结果在int范围内,(n+1)*n/2是在int范围的,但是(n+1)*n就不一定,中间可能会溢出,所以n的类型需要用long long。

#include <cstdio>
#include <cstdlib>

using namespace std;

int main()
{
    long long n;
    while(scanf("%lld",&n)!=EOF)
    {
        printf("%lld\n",(n+1)*n/2);
        printf("\n");
    }
}

E - Bitset  HDU - 2051

 这题就是让你把一个十进制数转成二进制数。

首先呢,十进制转成二进制,我们一直用短除法,然后将余数从反过来(从下到上)写一遍就是对应的二进制数了。

所以这个过程其实就是一直/2,除到商为0的时候,再往上一位一位地输出。如果我们用数组存储每一位的二进制的话,我们就需要将答案倒着输出,但是呢这个过程其实就是递归的过程,我们只需要递归结束后逐一输出即可。同样注意输出格式,每个结果后面需要一个换行。

#include <cstdio>

using namespace std;

void bt(int n)
{
    if (n==0) return;
    bt(n/2);
    printf("%d",n%2);
}
int main()
{
    int n;
    while(scanf("%d",&n)!=EOF)
    {
        bt(n);
        printf("\n");
    }
}

再来个非递归版的。先用while循环,将n一直除到0为止。在这里需要用数组存储一下每一位二进制,然后倒着输出即可。

#include <cstdio>

using namespace std;

int a[100];
int main()
{
    int n;
    while(scanf("%d",&n)!=EOF)
    {
        int cnt=0;
        while(n!=0)
        {
            a[++cnt]=n%2;
            n/=2;
        }
        for (int i=cnt;i>=1;i--)
            printf("%d",a[i]);
        printf("\n");
    }
}

 

D - 一只小蜜蜂... HDU - 2044 

题意:每只蜜蜂只能向右爬行,不能往回爬,求蜜蜂从a点到b点的所有路线。

 我们稍微改变一下题意,如果这题求的是1号点到点X的路线方案呢?

记从1号点到点x的方案数为f[x]

我们把前几个点的f[x]手算一下

f[1]=1

f[2]=1

f[3]=2

f[4]=3

f[5]=5

......

咦!这不是个斐波那契数列吗!

这真的就是斐波那契数吗?

是的!

那么为什么呢?我们冷静分析一下。

从3号点开始,3号点可以从1号点转移过来,也可以从2号点转移过来。4号点可以从3号点或者2号点转移过来。5号点.....

每个点会由他前两个点转移过来,对于点x来说,可以到达x-1的方案肯定可以到达x,同理可以到达x-2的方案肯定也可到达x。

所以我们可以得出来式子: f[x]=f[x-1]+f[x-2]。所以这就是一个斐波那契数列。

好了我们回到这道题。这里求的是从a点到b点的路线方案。那这个应该如何转换呢?其实也是一样的。显然这种情况下a点就是出发点,所以我们将a点看成1号点,那么b点就是斐波那契数列的第b-a+1项。所以答案就是f[b-a+1]

#include <cstdio>

using namespace std;

long long f[100];
int main()
{
    f[1]=1; f[2]=1;
    for (int i=3;i<50;i++)
        f[i]=f[i-1]+f[i-2];
    int n;
    scanf("%d",&n);
    for (int i=1;i<=n;i++)
    {
        int a,b;
        scanf("%d%d",&a,&b);
        printf("%lld\n",f[b-a+1]);
    }
}

F - 不容易系列之(3)—— LELE的RPG难题 HDU - 2045

 题意:用3种颜色涂n个方块,要求任意两个相邻的方块颜色不同,并且第一个方块和最后一个方块颜色也不同。求给这n个方块涂色有几种方案。

我一开始想这道题的时候是想用组合数学来解决的。但我数学实在是太差了一直推不出来qwq(其实是有公式的,只是我太菜了不会)

于是换了一个方向思考。

当n=1时方案是3

n=2时方案是6

n=3时方案是6

.....

这样写出来之后,emmm也看不出什么好的规律(当然如果你暴力打表看结果的话应该也是有可能直接看出来的)。

但我们可以这么想,如果第n块方案已经确定了,我们再添加一个方块,能否从

记f[n]为方块数量为n的方案数

首先,第1块和第n块的颜色肯定不同(废话),我们从第n-1块方块考虑。

如果第n-1块方块的颜色和第一块不同,这个时候第n个方块的颜色别无选择,已经固定了,只有一种选法。所以此时答案就是f[n-1](因为f[n-1]表示的就是满足题意的方案数,f[n-1]就表示了第n-1块和第1块颜色不同,且其他也两两不同的方案数)

如果第n-1块方块的颜色和第一块相同。这个时候首先f[n-2]满足条件(第n-2块与第一块颜色不同,且与第n-1块颜色不同),而且这时候,n-1块的颜色也已经固定了(和第1块相同),此时对于第n块来说,颜色就有两种选择(n-1与1颜色一样,只排除了一种)。所以此时 答案就是 2*f[n-2]

所以f[n]=f[n-1]+2*f[n-2]

但注意这个式子是从n>=4开始的,因为这里的N-2和1不能是同一块

做法就是类似斐波那契数列一样,我们预处理出方案数数组f[x],每次输入一个n,就可以输出答案了。

#include <iostream>

using namespace std;

long long f[55];
int main()
{
    int i,n;
    f[1]=3;
    f[2]=6;
    f[3]=6;
    for (i=4;i<=50;i++)
        f[i]=f[i-1]+f[i-2]*2;
    while(scanf("%d",&n)!=EOF)
    {
        printf("%lld\n",f[n]);
    }
}

 

 

C - Blocks POJ - 2363 

题意:给你n个1*1*1的小木块,让你拼成一个长方体,求拼出来的长方体最小的表面积是多少。

首先我们知道对于这些小木块组成的长方体,他的体积是已知的,就是N。

因为n不大,只有1e3。所以我们可以直接枚举可以组成的长方体的长宽高,然后算出每一个的表面积,最后在所有情况中选一个最小的就可以了。 时间复杂度O(n^2),不会超时。

具体细节可以看看代码的注释~

#include<cstdio>

const int INF=0x3f3f3f3f; // 0x3f3f3f3f的十进制为1061109567。在ACM中0x3f3f3f3f经常作为int型的“无穷大”
int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        int n;
        scanf("%d",&n);
        int ans=INF; // 初始化答案,将ans初始化成一个很大很大的数。 当然这个可以随便弄一个数(比如2e9),但一定要很大,不然更新最小值会出问题。
        for (int i=1;i<=n;i++) // i:枚举长方体的长
        {
            for (int j=1;j<=n;j++) // j:枚举长方体的高
            {
                if (i*j>n || n%(i*j)!=0) continue; // 如果长*宽>n(体积) ,或者长*宽不是n的因子。这两种情况都不存在高
                int k=n/i/j; // 长和宽确定之后,体积又已知,高也唯一确定了
                int s=i*j*2+i*k*2+j*k*2; // 计算此时的表面积
                if (s<ans) ans=s; // 更新答案
            }
        }
        printf("%d\n",ans);
    }
}

 

G - 叠筐 HDU - 2074

题意就不多说了,大家看样例应该就能看懂了。

非常直接明了的告诉你要干什么,只需要从外往内一层一层地填就行了(从内往外也可以)。听着挺简单的,但实现起来就没那么容易了。如何从外面这样一层一层填呢?仔细思考一下,我们可以先找一找规律。

设中心字符为a,外框为b

首先题目保证n是奇数,然后分析一下样例,显然对于每一个n,一共有(n+1)/2层,而且最中间的一个字符已经确定了(就是a)。所以此时我们从里往外可以推出来最外层的字符应该是哪一个。对于层数是奇数层的,我们可以推出来最外层字符也是a,偶数层的话最外层字符则为b。既然得出来了最外层字符,这个时候我们就可以从最外层开始一层一层颜色交替地涂色了。

同时我们注意到,最外层涂色的部分是第一行,第n行,第一列,第n列(除了整个图形的四个角)

第二层是第二行,第N-2行,第2列,第n-1列。

现在是不是规律就明显了~

最后特判一下,左上左下,右上右下,4个角的位置不输出。

#include <iostream>
#include <cstring>
using namespace std;

char c1,c2;
char ans[100][100];
int main()
{
    int i,j,n,cnt=0;
    while(scanf("%d %c %c",&n,&c1,&c2)!=EOF)
    {
        if (cnt!=0) printf("\n");
        cnt++;
        if (n==1)
        {
            printf("%c\n",c1);
            continue;
        }
        int x=n/2+1;
        ans[x][x]=c1;
        char now;
        if (x%2==0) now=c2;
        else now=c1;
        int l=1,r=n;
        while(l<r)
        {
            for (i=l;i<=r;i++)
            {
                ans[l][i]=now;
                ans[r][i]=now;
                ans[i][l]=now;
                ans[i][r]=now;
            }
            l++; r--;
            if (now==c1) now=c2;
            else now=c1;
        }
        for (i=1;i<=n;i++)
        {
            for (j=1;j<=n;j++)
            {
                if ((i==1 || i==n)  && (j==1 || j==n))
                {
                    printf(" ");
                    continue;
                }
                printf("%c",ans[i][j]);
            }
            printf("\n");
        }
    }
}

 

 

 

 

I - Basketball Exercise CodeForces - 1195C 

题意:有两排学生,两组序号都是从左到右为1到n,篮球教练想从中选出一些人来组建出一支身高和最高的队伍,人数不限,但选人方式有规则,从左往右选,他不会连续从同一排里选择2个人,也就是如果上一次选择了学生是第一排的,那么这次就不会在第一排中选,同理第二排也是,而且可以选择不选。

这道题有点超纲了......这其实是个简单的dp(动态规划),但我们还是可以大概看一下思路的。

首先对于每一个列来说,有三种选择,一种是选择第一个人加入队里,一种是选择第二个人加入队里,还有一种是一个人都不选。而对于某一个位置来说,他能否选择取决于和他同一排,在他前一个位置的数字是否被选择了。所以这就有递推的感觉了,第x个人能否选择是由第x-1个人递推过来的。

我们记dp[i][0]表示第 i 列 一个人都不选,得出来的前i个人能组成的最大身高。

dp[i][1]表示第i列选择第1个人,得出来的前i个人能组成的最大身高。

dp[i][2]表示第i列选择第2个人,得出来的前i个人能组成的最大身高。

所以显然dp[i][0]可以从前i-1个人组成的最大身高递推过来,因为它一个人都不选,没有任何限制。

我们就从第i-1个人的三个状态中的最大值递推过来。

得出:dp[i][0]=max(dp[i-1][0],max(dp[i-1][1],dp[i-1][2]))

而dp[i][1]的递推稍微有点限制。只有当第i-1列,第一行的数没有被选择的时候他才可以选择。所以第i-1列可以一个都不选,也可以选择第二行的数。所以我们从这两个状态取个最大值再加上当前位置的身高即可递推过来。

所以dp[i][1]=max(dp[i-1][0],dp[i-1][2])+a[i] (a[i]是第1行对应第i列的人的身高)

同理dp[i][2],只有当第i-1列,第二行的数没有被选择的时候,这个位置才可以被选择。所以也是第i-1列可以一个都不选,也可以只选择第一行的数。

同理可得 dp[i][2]=max(dp[i-1][0],dp[i-1][1])+b[i]

注意需要使用long long 存储

#include <iostream>
#include <cstring>
using namespace std;

const int maxn=1e5+50;
long long a[maxn],b[maxn],dp[maxn][4];
int main()
{
    int i,j,n,m;
    memset(dp,0,sizeof(dp)); // 初始化,开始的时候总身高都是0
    scanf("%d",&n);
    for (i=1;i<=n;i++)
        scanf("%lld",&a[i]);
    for (i=1;i<=n;i++)
        scanf("%lld",&b[i]);
    long long ans=0;
    for (i=1;i<=n;i++)
    {
        dp[i][0]=max(dp[i-1][0],max(dp[i-1][1],dp[i-1][2]));
        dp[i][1]=max(dp[i-1][0],dp[i-1][2])+a[i];
        dp[i][2]=max(dp[i-1][0],dp[i-1][1])+b[i];
        ans=max(max(ans,dp[i][2]),max(dp[i][0],dp[i][1])); // 在所有的答案中取个最大值
    }
    printf("%lld\n",ans);
}

 

B - 汉诺塔II HDU - 1207 

题意:在汉诺塔的基础上多加一根柱子,移动规则不变,求最少多少次移动可以将n个圆盘移动到目标柱子上。

首先我们回顾一下汉诺塔问题。假设要从A柱移动到C柱,设移动n个圆盘的最少移动次数为f[n]

在汉诺塔问题中,第一根柱子上有n个圆盘,我们先将上面的n-1个移动到B柱,这时候相当于n-1个圆盘从A柱借助C柱移动到B柱,本质就是汉诺塔问题的本质,所以此时的移动次数为f[n-1]。然后我们把A柱的最后一个圆盘直接移动到C柱,移动次数为1。接着我们再把B柱上的n-1个圆盘借助A柱移动到C柱,这个本质也是和一开始一样的,所以移动次数为f[n-1]。

所以汉诺塔问题得出来的递推公式为f[n]=2*f[n-1]+1

而且进一步我们还可以得出来O(1)的公式 f[n]=2^n-1

现在我们多了一根柱子,情况会怎么样呢?

同样地我们也可以类似地分析。假设要从A柱移动到D柱。设移动n个圆盘的最少移动次数为f[n]

在第一根柱子上,我们将连续x个圆盘从A柱借助C、D移动到B柱,这个时候的移动次数为f[x],然后我们再将剩下的n-x个圆盘,从A柱借助C柱移动到D柱(这个时候B柱用不了了,因为剩下的n-x个圆盘都比B柱上的大),这个问题就和原汉诺塔一样了(三根柱子),所以此时的移动次数为2^(n-x)-1(汉诺塔问题得出来的公式)。最后我们再将B柱上的x个圆盘,从B柱,借助A.、C柱移动到D柱,此时的移动次数还是f[x]。

所以我们得出来的公式为f[n]=2*f[x]+2^(n-x)-1 ( 1<=x<n)

显然对于不同的x来说得出来的结果不一样,既然我们要求的是最小值,我们可以枚举所有的x,算出对应的结果,最后取个最小值就可以了。

具体细节可以看一下注释。

#include<cstdio>
#include<cmath>
#include<cstring>
#include<iostream>
using namespace std;

const long long INF=0x3f3f3f3f3f3f3f3f; // 常用的初始化技巧,
long long f[100];
int main()
{
    memset(f,INF,sizeof(f)); // 将数组初始化成“无穷大”
    f[1]=1;
    f[2]=3;
    for (int i=3;i<=64;i++)
    {
        for (int x=1;x<i;x++)
        {
            if (i-x>=63) continue; // 防止2^63溢出 (long long 最大值为2^63-1)
            long long k=pow(2,i-x)-1;
            f[i]=min(f[i],k+f[x]*2); // 对所有小于i的x算出来的值取一个最小的
        }
    }
    int n;
    while(scanf("%d",&n)!=EOF)
    {
        printf("%lld\n",f[n]);
    }
}

 

H - Number Sequence HDU - 1005 

题意:f(1) = 1, f(2) = 1, f(n) = (A * f(n - 1) + B * f(n - 2)) mod 7. 

给定A,B,求出f[n]

一看这个递推式,和斐波那契很相似,第一反应应该是直接递推得出答案,一看数据范围。。n有1e9....打扰了.....这样O(N)递推肯定会超时。

这种时候我们就应该试着去找一找规律,看看能不能找出一些比较好的特点。

首先我们注意到mod 7,所以每一个f[n]的取值范围都是0~6,只有7种可能。

所以对于连续的两个位置,他们的取值方案有7*7=49种

而且显然,如果f[a]=f[x],f[a+1]=f[x+1] 则f[a+2]=f[x+2]

前两个数相同的话,第三个数必定相同。所以这可以形成一个循环节。我们只需要找出循环节的开始位置和长度就可以了,而且我们可以确保循环节肯定在49以内出现,以为所有可能性是49种,后面再出现肯定会有重复的,即后面再出现一定会形成循环节。

#include<cstdio>
#include<cmath>
#include<cstring>
#include<iostream>
using namespace std;

int vis[10][10]; // vis 记录数对(f[x],f[y]) 第一次出现的位置  
int f[100];
int main()
{
    int a,b,n;
    while(scanf("%d%d%d",&a,&b,&n)&&!(a==0&&b==0&&n==0))
    {
        f[1]=1;
        f[2]=1;
        memset(vis,0,sizeof(vis));
        vis[1][1]=1;
        int len=-1,st;
        for (int i=3;i<=min(50,n);i++)
        {
            f[i]=(a*f[i-1]+b*f[i-2])%7;
            if (vis[f[i-1]][f[i]]!=0)
            {
                st=vis[f[i-1]][f[i]];
                len=i-1-vis[f[i-1]][f[i]]; // 第二次出现的位置减去第一次出现的位置就是循环节的长度 
                break;
            }
            else vis[f[i-1]][f[i]]=i-1;
        }
        if (len==-1) printf("%d\n",f[n]); //  n比较小的时候还没有循环节,直接输出 
        else printf("%d\n",f[(n-st)%len+st]);  // 有循环节的话处理后输出 
    }
}

这题还有矩阵快速幂的解法,有兴趣的同学可以去学一下~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值