hiho221 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;
}