1. 程式人生 > >hiho221 Push Button II

hiho221 Push Button II

目錄

題意分析:

2.思路

ac程式碼:

時間限制: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;
}