参考博客: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; }