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

原题链接:Click
增强数据:Clickhtml

Solution

参考博客:Clickweb

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

dp

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

空间优化

空间复杂度也是 O ( n k ) O(nk) 的,但事实上咱们只须要用到 O ( k 2 ) O(k^2) 的内容,很容易想到滚动数组。
因而写出:svg

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数组,以下:spa

int *f[maxn];

而后赋值:.net

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

那么访问时,直接:指针

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

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

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

要快上很多,为何?

由于 f [ i ] f[i] 存的索引直接加上 j 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;
}
相关文章
相关标签/搜索