NOI Online入门组《跑步》详细题解报告

解法一:70分

分析

本题考察整数的拆分。
可用f[i][j]来表示数字i拆分成j个数。
以4为例。
若用两个数来组成4,即f[4][2]。则有两种方案:{3,1}与{2,2}。
{3,1}是从数字3加上数字1,得到的。{3}可表示为f[3][1]。
{2,2}是从{1,1}中的每个数加1得到的。{1,1}可表示为f[2][2]。
则f[4][2]= f[3][1] + f[2][2] = f[4-1][2-1]+f[4-2][2]。
所以有状态转移方程:f[i][j] = f[i -1][j - 1] + f[i - j][j]。

代码

#include<cstdio>
int f[5001][5001];

int main()
{
#ifndef ONLINE_JUDGE
    freopen("running.in", "r", stdin);
    freopen("running.out", "w", stdout);
#endif

    int n, p;
    scanf("%d %d", &n, &p);
    f[0][0] = 1;

    for(int i=1; i<=n; ++i)     //i代表被拆分的数
    {
        for(int j=1; j<=i; ++j) //j代表拆分成几个数
        {
            f[i][j] = (f[i-1][j-1] + f[i-j][j]) % p;
        }
    }

    int cnt = 0;
    for(int i=1; i<=n; ++i)
    {
        cnt = (cnt + f[n][i]) % p;
    }

    printf("%d", cnt);//输出

    return 0;
}

程序分析

以n = 4为例,递推过程如下表所示:

i

j

f[i][j]=f[i-1][j-1]+f[i-j][j]

意义

1

1

f[1][1]=f[0][0]+f[0][1]=1

1拆成1个数,有1种方案,即{1}

2

1

f[2][1]=f[1][0]+f[1][1]=1

2拆成1个数,有1种方案,即{2}

2

f[2][2]=f[1][1]+f[0][2]=1

2拆成2个数,有1种方案,即{1,1}

3

1

f[3][1]=f[2][0]+f[2][1]=1

3拆成1个数,有1种方案,即{3}

2

f[3][2]=f[2][1]+f[1][2]=1

3拆成2个数,有1种方案,即{2,1}

3

f[3][3]=f[2][2]+f[0][3]=1

3拆成3个数,有1种方案,即{1,1,1}

4

1

f[4][1]=f[3][0]+f[3][1]=1

4拆成1个数,有1种方案,即{4}

2

f[4][2]=f[3][1]+f[2][2]=2

4拆成2个数,有2种方案,即{3,1},{2,2}

3

f[4][3]=f[3][2]+f[1][3]=1

4拆成3个数,有1种方案,即{2,1,1}

4

f[4][4]=f[3][3]+f[0][4]=1

4拆成4个数,有1种方案,即{1,1,1,1}

 

解法二:80分

分析

用一维数组可以节省内存开销。j表示要拆的数,i表示最多拆成i个数。状态转移方程为:f[j] = (f[j] + f[j-i])。
核心代码为:

    for(int i = 1; i <= n; ++i)             //i表示可以折成几个数
    {
        for(int j = i; j <= n; ++j)         //j表示对哪个数进行拆分
        {
            f[j] = (f[j] + f[j-i]);  //状态转移
        }
    }

以n = 4为例,初始化f[0] = 1,f[1] = f[2] = f[3] = f[4] = f[5] = 0。这里f[0] = 1是因为0可拆分为{0},共有一种表示方法。

(1)i = 1,表示数拆分出来的个数不超过1个,同时在上一步的基础上加{1}

f[1] = f[1] + f[0] = 0 + 1 = 1,等号右边的f[1] = 0表示先前1没有被拆分过,f[0] = 1表示的是要在{0}的基础上加{1},即{0} + {1} = {1},所以1可以拆分成不超过一个数的集合为{1},拆分的总数为f[1] = 1。
f[2] = f[2] + f[1] = 0 + 1 = 1,等号右边的f[2] = 0表示先前2没有被拆分过,f[1] = 1表示的是要在{1}的基础上加{1},即{1} + {1} = {2},所以2可以拆分成不超过一个数的集合为{2},拆分的总数为f[2] = 1。
f[3] = f[3] + f[2] = 0 + 1 = 1,等号右边的f[3] = 0表示先前3没有被拆分过,f[2] = 1表示的是要在{2}的基础上加{1},即{2} + {1} = {3},所以3可以拆分成不超过一个数的集合为{3},拆分的总数为f[3] = 1。
f[4] = f[4] + f[3] = 0 + 1 = 1,等号右边的f[4] = 0表示4先前没有被拆分过,f[3] = 1表示的是要在{3}的基础上加{1},即{3} + {1} = {4},所以4可以拆分成不超过一个数的集合为{4},拆分的总数为f[4] = 1。

(2)i = 2,表示数拆分出来的个数不超过2个,同时表示要在上一步的基础上加{1, 1}

f[2] = f[2] + f[0] = 1 + 1 = 2,等号右边的f[2] = 1表示的是{2},f[0] 表示的是{0}, {0} + {1, 1} = {0, 0} + {1, 1} = {1, 1},所以2可拆分成不超过两个数的集合:{2}和{1, 1},拆分的总数为f[2] = 2
f[3] = f[3] + f[1] = 1 + 1 = 2,等号右边的f[3] = 1表示的是{3},f[1] 表示 的是{1},{1} + {1, 1} = {1, 0} + {1, 1} = {2, 1},所以3可拆分成不超过两个数的集合:{3}和{2, 1},拆分的总数为f[3] = 2。
f[4] = f[4] + f[2] = 1 + 2 = 3,等号右边的f[4] = 1表示的是{4},f[2] 表示 的是{2}和{1, 1},分别加上{1, 1}后,{2} + {1, 1} = {2, 0} + {1, 1} = {3, 1}, {1, 1} + {1, 1} = {2, 2},所以4可拆分成不超过两个数的集合:{4}, {3, 1}, {2, 2},拆分的总数为f[4] = 3。

(3)i = 3,表示数拆分出来的个数不超过三个,同时表示要在上一步的基础上加{1, 1, 1}

f[3] = f[3] + f[0] = 2 + 1 = 3,等号右边的f[3] = 2表示的是上一步的{3}和{2, 1},f[0] 表示的是要在{0}的基础上{1, 1, 1}, 即{0} + {1, 1, 1} = {0, 0, 0} + {1, 1, 1} = {1, 1, 1},所以3可拆分成不超过三个数的集合:{3}和{2, 1}, {1, 1, 1}。拆分的总数为f[3] = 3
f[4] = f[4] + f[1] = 3 + 1 = 4,等号右边的f[4] = 1表示的是上一步中的{4}, {3, 1}, {2, 2},f[1] 表示的是要在{1}的基础上{1, 1, 1},即{1} + {1, 1, 1} = {1, 0, 0} + {1, 1, 1} = {2, 1, 1},所以4可拆分成不超过三个数的集合:{4}, {3, 1}, {2, 2}, {2, 1, 1},拆分的总数为f[4] = 3。

(4)i = 4,表示数拆分出来的个数不超过四个,同时表示要在上一步的基础上加{1, 1, 1, 1}

f[4] = f[4] + f[0] = 4 + 1 = 5,等号右边的f[4] = 4表示的是上一步的{4}, {3, 1}, {2, 2}, {2, 1, 1},f[0] 表示的是要在{0}的基础上{1, 1, 1, 1}, 即{0} + {1, 1, 1, 1} = {0, 0, 0, 0} + {1, 1, 1, 1} = {1, 1, 1, 1},所以4可拆分成不超过四个数的集合:{4}, {3, 1}, {2, 2}, {2, 1, 1}, {1,1,1,1}。拆分的总数为f[4] = 5

代码

#include<cstdio>
int f[100001];

int main()
{
    int n, p;
    scanf("%d %d", &n, &p);
    f[0] = 1;
    for(int i = 1; i <= n; ++i)             //i表示可以折成几个数
    {
        for(int j = i; j <= n; ++j)         //j表示对哪个数进行拆分
        {
            f[j] = (f[j] + f[j-i]) % p;  //状态转移
        }
    }

    printf("%d", f[n]);//输出

    return 0;
}

解法三:100分

分析

可把n分成两部分,小于等于√n的部分和大于√n的部分。根据乘法原理,将两部分用乘法原理乘起来即可。
以n = 6为例,如下图所示,左侧拆分出来的数小于sqrt(6),即拆成1和2。右侧拆分出来的数不小于sqrt(6),即拆成3、4、5或6。
按照乘法原理合并:
左侧的{0}和右侧的{6}、{3,3}合并成{6},{3,3},这是两种。
左侧的{1}和右侧的{5}合并成{1,5},这是一种。
左侧的{2}、{1,1}和右侧的{4}合并成{2,4}、{1,1,4},这是两种。
左侧的{2,1}、{1,1,1}和右侧的{3}合并成{2,1,3}、{1,1,1,3},这是两种。
左侧的{2,2,2}、{2,2,1,1}、{2,1,1,1,1}、{1,1,1,1,1,1}和右侧的{0}合并成{2,2,2}、{2,2,1,1}、{2,1,1,1,1}、{1,1,1,1,1,1},这是四种。
所以,6的拆分方案总共有2+1+2+2+4=11种。

在这里插入图片描述

代码

#include <bits/stdc++.h>
using namespace std;

#define LL long long
const int N = 100005;

int f[N];
int g[400][N];

int main()
{
#ifndef ONLINE_JUDGE
    freopen("running3.in", "r", stdin);
    freopen("running3.out", "w", stdout);
#endif

    int n, p;
    cin >> n >> p;
    int m = sqrt(n) + 1;
    
    //小于等于根号n的数的拆分方法
    f[0] = 1;
    for (int i = 1; i < m; i++)
    {
        for (int j = i; j <= n; j++)
        {
            f[j] += f[j - i];
            f[j] %= p;
        }
    }
    
    //大于根号n的数的拆分 方法
    g[0][0] = 1;
    for (int i = 1; i < m; i++)//i表示拆分成几个数
    {
        for (int j = i; j <= n; j++)//j表示被拆分的数
        {
            g[i][j] = g[i][j - i];
            if (j >= m)
            {
                g[i][j] += g[i - 1][j - m];
            }
            g[i][j] %= p;
        }
    }

    int ans = 0;
    for (int i = 0; i <= n; i++)
    {
        LL sum = 0;
        for (int j = 0; j < m; j++)
        {
            sum += g[j][n - i];
        }
        sum %= p;
        ans = (ans + f[i] * sum) % p;
    }

    cout << ans << endl;
    
    return 0;
}

程序分析:

这里有三次for循环,循环的次数为3n√n,所以时间复杂度为O(n√n)。
下面以n = 6为例,推导每次循环的过程。

(1)第一次循环

i

j

f[j] = f[j] + f[j - i]

意义

1

1

f[1] = f[1] + f[0] = 1

1拆成不大于1的数,有1种拆法,即{1}

2

f[2] = f[2] + f[1] = 1

2拆成不大于1的数,有1种拆法,即{1,1}

3

f[3] = f[3] + f[2] = 1

3拆成不大于1个数,有1种拆法,即{1,1,1}

4

f[4] = f[4] + f[3] = 1

4拆成不大于1个数,有1种拆法,即{1,1,1,1}

5

f[5] = f[5] + f[4] = 1

5拆成不大于1个数,有1种拆法,即{1,1,1,1,1}

6

f[6] = f[6] + f[5] = 1

6拆成不大于1个数,有1种拆法,即{1,1,1,1,1,1}

2

2

f[2] = f[2] + f[0] = 2

2拆成不大于2的数,有2种拆法,即{2},{1,1}

3

f[3] = f[3] + f[1] = 2

3拆成不大于2的数,有2种拆法,即{2},{1,1,1}

4

f[4] = f[4] + f[2] = 3

4拆成不大于2的数,有3种拆法,即{2,2},{2,1,1},{1,1,1,1}

5

f[5] = f[5] + f[3] = 3

5拆成不大于2的数,有3种拆法,即{2,2,1},{2,1,1,1},

{1,1,1,1,1}

6

f[6] = f[6] + f[4] = 4

6拆成不大于2的数,可在4的每种拆法中加一个2,加上次拆成的六个1,共有4种拆法,即{2,2,2},{2,2,1,1},

{2,1,1,1,1}, {1,1,1,1,1,1}

 

(2)第二次循环

i

j

g[i]][j]

意义

1

1

g[1][1]=g[1][0]=0

1拆分成一个大于sqrt(6)的数,方案有0种

2

g[1][2]=g[1][1]=0

2拆分成一个大于sqrt(6)的数,方案有0种

3

g[1][3]=g[1][2]=0

g[1][3]=g[1][3]+[0][0]=1

3拆分成一个大于sqrt(6)的数,方案有1种,即{3}

4

g[1][4]=g[1][3]=1

g[1][4]=g[1][4]+g[0][1]=1

4拆分成一个大于sqrt(6)的数,方案有1种,即{4}

5

g[1][5]=g[1][4]=1

g[1][5]=g[1][5]+g[0][2]=1

5拆分成一个大于sqrt(6)的数,方案有1种,即{5}

6

g[1][6]=g[1][5]=1

g[1][6]=g[1][6]+g[0][3]=1

5加1后变成6,则6有一种大于sqrt(6)的拆法,即{6}

2

2

g[2][[2]=g[2][0]=0

2拆分成两个大于sqrt(6)的数,方案有0种

3

g[2][3]=g[2][1]=0

g[2][3]=g[2][3]+g[1][0]=0

3拆分成两个大于sqrt(6)的数,方案有0种

4

g[2][4]=g[2][2]=0

g[2][4]=g[2][4]+g[1][1]=0

4拆分成两个大于sqrt(6)的数,方案有0种

5

g[2][5]=g[2][3]=0

g[2][5]=g[2][5]+g[1][2]=0

5拆分成两个大于sqrt(6)的数,方案有0种

6

g[2][6]=g[2][4]=0

g[2][6]=g[2][6]+g[1][3]=1

在4拆成两个大于sqrt(6)的方案数的基础上,每个数加1则变成6的拆分方案数,这个方案数是0。再在3的拆分基础上加1个3,得到1种方案,即{3,3}

 

(3)第三次循环

i

两部分合并的方案数

意义

0

(g[0][6]+g[1][6]+g[2][6]) * f[0] = 2

{6},{3,3}与{0}合并后成为{6},{3,3},两种方案

1

(g[0][5]+g[1][5]+g[2][5]) * f[1] = 1

{5}与{1}合并成{5,1},一种方案

2

(g[0][4]+g[1][4]+g[2][4]) * f[2] = 2

{4}与{2},{1,1}合并成{4,2},{4,1,1},两种方案

3

(g[0][3]+g[1][3]+g[2][3]) * f[3] = 2

{3}与{2,1},{1,1,1}合并成{3,2,1},{3,1,1,1},两种方案

4

(g[0][2]+g[1][2]+g[2][2]) * f[4] = 0

没有方案

5

(g[0][1]+g[1][1]+g[2][1]) * f[5] = 0

没有方案

6

(g[0][0]+g[1][0]+g[2][0]) * f[6] = 4

{0}与{2,2,2},{2,2,1,1},{2,1,1,1,1},{1,1,1,1,1,1}合并成{2,2,2},{2,2,1,1},{2,1,1,1,1},{1,1,1,1,1,1},四种方案

 

了解信息学奥赛请加微信307591841(QQ同号)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值