參考博客:Clickweb
題目意思很是明確了,這是一道組合數學的題目。我就直接講dp解法了。數組
題意能夠轉化爲將
個蘋果放進
個盒子裏,而且不容許空盒。
設
表明將
個蘋果放入
個盒子中,那麼咱們用解決這類問題的經常使用方法來分析:
咱們必須先保證每一個盒子非空,所以在
個蘋果中選出
個放入每一個盒子。
此時咱們剩餘
個蘋果,咱們就是要往已有的一層蘋果上加
蘋果,求此時的方案數。
如今
個蘋果能夠任意分配了,也就是分紅
份、
份、
份都是合法的……
獲得轉移方程:
枚舉
,隨後枚舉
,隨後枚舉
,三層循環便可得出答案。
時間複雜度爲
,預期得分70分。
這個或許能夠套樹狀數組優化一下求和……
那麼複雜度是
,然而最大的範圍
達到了
億的大小,再加上個
鐵定超時。
而後你能夠發現:
爲何會有這樣的奇特之處呢?由於
就是
和
的差值,那麼同增同減一個
,dp數組的一維下標是不變的,只是二維的
會少一個
,那麼咱們把這個加上就行了。
據此寫出轉移方程:
兩層循環便可轉移,複雜度就降到
了,因爲常數小,能夠經過本題。
但交上去……MLE!app
空間複雜度也是
的,但事實上咱們只須要用到
的內容,很容易想到滾動數組。
因而寫出: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徹底是常數問題,將內層循環的兩個判斷改爲取min逆序後依然沒法經過。
常數影響最大的就是pos函數了,因而改爲了指針映射,成功AC!優化
咱們考慮要如何避免pos函數的高耗時,固然想到了預處理。預處理一遍pos數組,直接訪問便可,這應該也是能卡過的(沒有嘗試)。
但還有一種更有技巧性、效率更高的方法:指針。
開一個f數組,以下:spa
int *f[maxn];
而後賦值:.net
f[i] = dp[pos(i)];
那麼訪問時,直接:指針
f[i][j] = ....
爲何會快?這個很顯然了吧……事實上,這種方法比:
dp[pos[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左右……
#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; }