[LuoguP1025][数据加强]数的划分

原题连接:Click
加强数据:Click

Solution

参考博客:Click

题目意思非常明确了,这是一道组合数学的题目。我就直接讲dp解法了。

dp

题意可以转化为将\(n\)个苹果放进\(k\)个盒子里,并且不允许空盒。
设\(f[i][j]\)代表将\(i\)个苹果放入\(j\)个盒子中,那么我们用解决这类问题的常用方法来分析:
我们必须先保证每个盒子非空,因此在\(i\)个苹果中选出\(j\)个放入每个盒子。
此时我们剩余\(i-j\)个苹果,我们就是要往已有的一层苹果上加\(i-j\)苹果,求此时的方案数。
现在\(i-j\)个苹果可以任意分配了,也就是分成\(1\)份、\(2\)份、\(3\)份都是合法的……
得到转移方程:
\[ dp[i][j] = \sum_{k=1}^jdp[i-j][k]\]
枚举\(i\),随后枚举\(j\),随后枚举\(k\),三层循环即可得出答案。
时间复杂度为\(O(nk^2)\),预期得分70分。
这个或许可以套树状数组优化一下求和……
那么复杂度是\(O(nk\log k)\),然而最大的范围\(nk\)达到了\(1.2\)亿的大小,再加上个\(\log\)铁定超时。
然后你可以发现:
\[dp[i-1][j-1] = \sum_{k=1}^{j-1}dp[i-j][k]\]
为什么会有这样的奇特之处呢?因为\(i-j\)就是\(i\)和\(j\)的差值,那么同增同减一个\(1\),dp数组的一维下标是不变的,只是二维的\(k\)会少一个\(dp[i-j][j]\),那么我们把这个加上就好了。
据此写出转移方程:
\[dp[i][j] = dp[i-1][j-1] + dp[i-j][j]\]
两层循环即可转移,复杂度就降到\(O(nk)\)了,由于常数小,可以通过本题。
但交上去……MLE!

空间优化

空间复杂度也是\(O(nk)\)的,但事实上我们只需要用到\(O(k^2)\)的内容,很容易想到滚动数组。
于是写出:

inline int pos(const int &x)
{
    return (x % 600) + 1;
}
int main()
{
    scanf("%d%d", &n, &k);
    dp[pos(0)][0] = 1;
    int i, j;
    for (i = 1; i <= n; ++i)
    {
        memset(dp[pos(i)], 0, sizeof(dp[pos(i)]));
        for (j = 1; j <= k && j <= i; ++j)
            dp[pos(i)][j] = (dp[pos(i-j)][j] + dp[pos(i-1)][j - 1]) % 10086;
    }
    printf("%d", dp[pos(n)][k]);
    return 0;
}

个人预期是能AC了,但实际上……第15个点冷酷无情地T了。
评测机跑得不够快

拯救TLE

吸了氧还是不能拯救世界之后,我想起了当年用的一种奇淫技巧……
显然此时TLE完全是常数问题,将内层循环的两个判断改成取min逆序后依然无法通过。
常数影响最大的就是pos函数了,于是改成了指针映射,成功AC!

指针映射

我们考虑要如何避免pos函数的高耗时,当然想到了预处理。预处理一遍pos数组,直接访问即可,这应该也是能卡过的(没有尝试)。
但还有一种更有技巧性、效率更高的方法:指针。
开一个f数组,如下:

int *f[maxn];

然后赋值:

f[i] = dp[pos(i)];

那么访问时,直接:

f[i][j] = ....

为什么会快?这个很显然了吧……事实上,这种方法比:

dp[pos[i]][j] = ....

要快上不少,为什么?

因为\(f[i]\)存的索引直接加上\(j\)就能得到地址,我们实际上避免了两个大数的乘法,而使其变成了加法。
举例:
原先访问方式:

dp[x?(m+2)+y]

进行了一次乘法一次加法
解析一下就是:

return dp + (x * (m+2) + y);

而现在的访问方式:

(f[x]+y)

解析一下就是:

return (f + x) + y;

效率提升相当显著。
以上这段是直接copy原来那篇树上背包的优化中的内容……
同时注意我们的预处理方式:

int pointer = 0;
++pointer;
if(pointer >= 600)
    pointer -= 600;

可以避免反复求余的预处理效率损失。
最后第15个点跑了500ms左右……

Code

#include <cstdio>
#include <cstring>
using namespace std;
int n, k;
int dp[610][610];
int *f[200100];
inline int min(const int &a,const int &b){return a<b?a:b;}
int main()
{
    scanf("%d%d", &n, &k);
    int p = 0;
    for (int i = 0; i <= n; ++i)
    {
        if (p >= 600)
            p -= 600;
        f[i] = dp[p + 1];
        ++p;
    }
    f[0][0] = 1;
    int i, j;
    for (i = 1; i <= n; ++i)
    {
        memset(f[i], 0, sizeof(f[i]));
        for (j = min(k,i); j; --j)
            f[i][j] = (f[i - j][j] + f[i - 1][j - 1]) % 10086;
    }
    printf("%d", f[n][k]);
    return 0;
}

原文地址:https://www.cnblogs.com/Clouder-Blog/p/12203227.html

时间: 2024-10-11 03:32:56

[LuoguP1025][数据加强]数的划分的相关文章

luoguP1025+codevs 1039 数的划分 x

luoguP1025 + codevs1039 数的划分 2001年NOIP全国联赛提高组 时间限制: 1 s 空间限制: 128000 KB 题目等级 : 黄金 Gold 题目描述 Description 将整数n分成k份,且每份不能为空,任意两种划分方案不能相同(不考虑顺序).例如:n=7,k=3,下面三种划分方案被认为是相同的.1 1 5 1 5 1 5 1 1问有多少种不同的分法. 输入描述 Input Description 输入:n,k (6<n<=200,2<=k<=

C语言 &#183; 数的划分

算法提高 数的划分 时间限制:1.0s   内存限制:256.0MB 问题描述 一个正整数可以划分为多个正整数的和,比如n=3时: 3:1+2:1+1+1: 共有三种划分方法. 给出一个正整数,问有多少种划分方法. 输入格式 一个正整数n 输出格式 一个正整数,表示划分方案数 样例输入 3 样例输出 3 数据规模和约定 n<=100 作者注释:递归问题.(本题运行超时) step表示当前剩余的数需要分成的份数;把n分成k份,只需第一个数等于i,计算从i等于1一直到i等于n/k,然后把剩余的n-i

codevs 1039 数的划分 x

1039 数的划分 2001年NOIP全国联赛提高组 时间限制: 1 s 空间限制: 128000 KB 题目等级 : 黄金 Gold 题目描述 Description 将整数n分成k份,且每份不能为空,任意两种划分方案不能相同(不考虑顺序).例如:n=7,k=3,下面三种划分方案被认为是相同的.1 1 5 1 5 1 5 1 1问有多少种不同的分法. 输入描述 Input Description 输入:n,k (6<n<=200,2<=k<=6) 输出描述 Output Desc

codevs——1039 数的划分

1039 数的划分 2001年NOIP全国联赛提高组 时间限制: 1 s 空间限制: 128000 KB 题目等级 : 黄金 Gold 题解 题目描述 Description 将整数n分成k份,且每份不能为空,任意两种划分方案不能相同(不考虑顺序).例如:n=7,k=3,下面三种划分方案被认为是相同的.1 1 5 1 5 1 5 1 1问有多少种不同的分法. 输入描述 Input Description 输入:n,k (6<n<=200,2<=k<=6) 输出描述 Output D

洛谷 P1025 数的划分

P1025 数的划分 题目描述 将整数n分成k份,且每份不能为空,任意两个方案不相同(不考虑顺序). 例如:n=7,k=3,下面三种分法被认为是相同的. 1,1,5; 1,5,1; 5,1,1; 问有多少种不同的分法. 输入输出格式 输入格式: n,k (6<n<=200,2<=k<=6) 输出格式: 一个整数,即不同的分法. 输入输出样例 输入样例#1: 复制 7 3 输出样例#1: 复制 4 说明 四种分法为:1,1,5;1,2,4;1,3,3;2,2,3; 思路:数据范围很小

NOIP2001 数的划分

题二 数的划分(20分) 问题描述 将整数n分成k份,且每份不能为空,任意两份不能相同(不考虑顺序). 例如:n=7,k=3,下面三种分法被认为是相同的. 1,1,5; 1,5,1; 5,1,1; 问有多少种不同的分法. 输入:n,k (6<n<=200,2<=k<=6) 输出:一个整数,即不同的分法. 样例 输入: 7 3 输出:4 {四种分法为:1,1,5;1,2,4;1,3,3;2,2,3;} [思路] 递推. 递推式d[i][j]=d[i-1][j-1]+d[i-j][j]

freemarker导出word——让表格数据行数 列数自动变化

行数.列数变化只需定义一个List<List<T>> freemarker遍历的话,只需要使用freemarker的标记性语言<#list report.qc_third_agentTable as  table2_tr>遍历即可,如图 实现的效果 freemarker导出word--让表格数据行数 列数自动变化,布布扣,bubuko.com

数的划分(动规)

数的划分 总时间限制:  1000ms 内存限制:  65536kB 描述 将整数n分成k份,且每份不能为空,任意两份不能相同(不考虑顺序). 例如:n=7,k=3,下面三种分法被认为是相同的. 1,1,5: 1,5,1: 5,1,1: 问有多少种不同的分法. 输出:一个整数,即不同的分法. 输入 两个整数n,k (6 < n <= 200,2 <= k <= 6),中间用单个空格隔开. 输出 一个整数,即不同的分法. 样例输入 7 3 样例输出 4 提示 四种分法为:1,1,5:

js中如何将数据获得2位小数以及对数据进行千分位划分

js中toFixed(n) 方法可把 数字四舍五入为指定小数位数n的数字,注意:这个方法只能对数据类型为Number的数据起作用,包括float,int等.例如: 123.12345.toFixed(2); "123.12" 对数据进行千分位划分采用这样的一个语句就行了:.replace(/\d{1,3}(?=(\d{3})+(\.\d*)?$)/g, '$&,').注意:replace()方法只能对字符串类型起作用,而我们队数据进行千分位划分的时候通常是对数值进行划分,因此在