目录
题目1 : Push Button II
时间限制:10000ms
单点时限:1000ms
内存限制:256MB
描述
There are N buttons on the console. Each button needs to be pushed exactly once. Each time you may push several buttons simultaneously.
Assume there are 4 buttons. You can first push button 1 and button 3 at one time, then push button 2 and button 4 at one time. It can be represented as a string "13-24". Other pushing way may be "1-2-4-3", "23-14" or "1234". Note that "23-41" is the same as "23-14".
Given the number N your task is to find the number of different valid pushing ways.
输入
An integer N. (1 <= N <= 1000)
输出
Output the number of different pushing ways. The answer would be very large so you only need to output the answer modulo 1000000007.
样例输入
3
样例输出
13
题意分析:
1.题是什么?
将问题抽象化就是给你n个数字1~n,将所有数字以'-'分块,要输出的答案就是不重复的分块方法总数(由于总数太大要求余)
而什么是重复呢,分块个数相同且每个对应的分块中的数字组合是相同的则视为重复,就好像现在要计算1234四个数字的分块方法,其中两种方法12-34与21-43,第一个分块内都是1和2,第二个分块内都是3和4,故而他们是重复的.
2.思路
这道题是hiho220的问题变形,故而可以顺手了解一下我关于 hiho220 的解题博客,那道题是要输出所有的方法是怎样的,n最大为8而已故而使用的dfs填空,这道题其实题干一模一样,不过改为要求输出所有方法数目,并不需要关心每个方法具体是怎样的组合,相应的n也变为上限1000,故而原本的dfs填空必爆.需要改变解法.
我们发现我们现在只需要输出所有方法数目,并不关心每个方法具体组合,这种问题特征几乎是线性dp的标志.而题中n为1时答案确定为1,故而更坚定了是线性dp.
现在方向确定为线性dp后判断时间空间,1000的数据量级做n*n的dp时间空间都没问题,然后思考最关键的dp递推公式,线性dp的关键在于找到问题在n处的答案与前方在n-1处的答案的联系即递推关系.
针对与这个问题,我们列举当n为2时,答案为3: 12 1-2 2-1这三种,我们也可以将之看作:
?-?12-? (1)
?-?1-?-?2-? (2)
?-?2-?-?1-? (3)
?表示为空,我们会发现如果我们想在n为2的答案的基础上推出n为3的答案,一定是基于n为2的某个答案,将数字3加入其某个分块或使数字3自成一个分块,上面的?就是代表所有的这两种情况.
dp[i][j]中i与j的意义设计是线性dp最关键的一点,递推数组最初我设计为二维,dp[1000][1000],dp[i][j]我的设计中表示n为i时分块为j个的答案种数.
而就像dp[3][2],表示n为3时有2个分块的答案总数,它自然来自于n=2时的答案的延伸,也就是
(1).n=2时对分块为1的答案将3自成一个分块,1+1=2个分块
(2).n=2时对分块为2的答案将3加入其中某个分块
只有这两种情况可以延伸出dp[3][2].故而递推公式 dp[i][j]=j*dp[i-1][j-1]+j*dp[i-1][j];
for(int i=0;i<=n;i++) for(int j=0;j<=n;j++) dp[i][j]=0;
for(int i=1;i<=n;i++){
dp[i][1]=1;
for(int j=2;j<=n;j++) dp[i][j]=(j*(dp[i-1][j-1]+dp[i-1][j]))%mod;
}
用时13ms,这里发现n的答案种数仅仅与n-1有关,顺手用了个滚动数组节约一下空间,不懂滚动数组可以来我的博客先了解一下 滚动数组 优化后:
int now=0;
for(int i=1;i<=n;i++){
now=(now+1)%2;
dp[now][1]=1;
for(int j=2;j<=n;j++) dp[now][j]=(j*(dp[(now+1)%2][j-1]+dp[(now+1)%2][j]))%mod;
dp[now][i+1]=0; //特殊的初始化方式
}
用时6ms,然后又发现好像滚动数组都用不着,因为数据的递推方式很特别,于是最终发现了最优解法建立于一维数组:
for(int i=1;i<=n;i++){
dp[1]=1;
for(int j=i;j>=2;j--) dp[j]=(j*(dp[j-1]+dp[j]))%mod;
dp[i+1]=0; //特殊的初始化方式
}
用时3ms AC,再想了想.....应该没什么可优化的了,花15分钟从13ms优化到了3ms,用900000ms的时间换来的10ms的优化,emmmm....不知道说什么好.....
ac代码:
#include <stdio.h>
typedef long long ll;
const int maxn=1005;
const ll mod=1000000007;
ll dp[maxn];
void solve(){
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++){
dp[1]=1;
for(int j=i;j>=2;j--) dp[j]=(j*(dp[j-1]+dp[j]))%mod;
dp[i+1]=0; //特殊的初始化方式
}
ll ans=0;
for(int i=1;i<=n;i++) ans=(ans+dp[i])%mod;
printf("%lld\n",ans);
}
//https://blog.csdn.net/qq_31964727/article/details/82886990 解题博客了解一下
int main(){
solve();
return 0;
}