[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;
}
相關文章
相關標籤/搜索