1. 程式人生 > >51Nod 1296 - 有限制的排列(DP)

51Nod 1296 - 有限制的排列(DP)

【題目描述】
在這裡插入圖片描述

【思路】
做這道題首先要知道一種全排列的生成方式:如果要生成 [ 1 , n ] [1,n] 的全排列,考慮遞推關係,如果現在所有 [

1 , n 1 ] [1,n-1] 的排列都是已知的,那麼假設 [ 1
, n ] [1,n]
中的一個排列末尾元素為 x x ,那麼只要把 [
1 , n 1 ] [1,n-1]
中所有 > = x >=x 的元素都 + 1 +1 ,然後把 x x 放在第 n n 項,就能構造出一個 [ 1 , n ] [1,n] 的排列,並且前面的 n 1 n-1 項之間相互大小關係是不變的

首先根據輸入資料處理出一個數組 p p ,表示相鄰兩項的關係, p [ i ] = = 1 p[i]==1 表示 a [ i ] > a [ i + 1 ] a[i]>a[i+1] p [ i ] = = 1 p[i]==-1 表示 a [ i ] < a [ i + 1 ] a[i]<a[i+1] p [ i ] = = 0 p[i]==0 表示無限制,然後

d p [ i ] [ j ] dp[i][j] 表示 數字 [ 1 , i ] [1,i] 在前 i i 個位置,以 j j 為結尾生成的合法排列數目,有如下狀態轉移方程

d p [ i + 1 ] [ j ] = { k = 1 j 1 d p [ i ] [ k ]     ( p [ i ] = = 1 ) k = j i d p [ i ] [ k ]     ( p [ i ] = = 1 ) k = 1 i d p [ i ] [ k ]     ( p [ i ] = = 0 ) dp[i+1][j]=\begin{cases} \sum_{k=1}^{j-1}dp[i][k] \ \ \ (p[i]==-1) \\ \sum_{k=j}^{i}dp[i][k] \ \ \ (p[i]==1) \\ \sum_{k=1}^{i}dp[i][k] \ \ \ (p[i]==0)\end{cases} 遞推邊界從 d p [ 1 ] [ 1 ] = 1 dp[1][1]=1 開始,很明顯可以通過字首和來優化這個遞推方程,在遞推過程中用一個 s u m sum 陣列記錄字首和 s u m [ j ] = k = 1 j d p [ i ] [ j ] sum[j]=\sum_{k=1}^{j}dp[i][j] ,每次用 s u m sum 遞推,然後不斷更新 s u m sum

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod=1000000007;
const int maxn=5005; 

int n,k1,k2;
int p[maxn];//p[x]=-1表示a[x]<a[x+1],p[x]=1表示a[x]>a[x+1],p[x]=0表示都行 
int dp[maxn][maxn];
int sum[maxn];

int main(){
	scanf("%d%d%d",&n,&k1,&k2);
	for(int i=1;i<=k1;++i){
		int x;
		scanf("%d",&x);
		++x;
		p[x-1]=1;
		p[x]=-1;
	}
	for(int i=1;i<=k2;++i){
		int x;
		scanf("%d",&x);
		++x;
		p[x-1]=-1;
		p[x]=1;
	}
	
	sum[1]=dp[1][1]=1;
	for(int i=1;i<n;++i){
		for(int j=1;j<=i+1;++j){
			if(p[i]==-1){
				dp[i+1][j]=sum[j-1];
			}
			else if(p[i]==1){
				dp[i+1][j]=((sum[i]-sum[j-1])%mod+mod)%mod;
			}
			else{
				dp[i+1][j]=sum[i];
			}
		}
		for(int j=1;j<=i+1;++j){
			sum[j]=(sum[j-1]+dp[i+1][j])%mod;
		}
	}
	
	printf("%d\n",sum[n]);
	return 0;
}

可以看到上面的程式碼中 d p [ i ] [ j ] dp[i][j] 的第一維完全可以省去,進一步優化記憶體

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod=1000000007;
const int maxn=5005; 

int n,k1,k2;
int p[maxn];//p[x]=-1表示a[x]<a[x+1],p[x]=1表示a[x]>a[x+1],p[x]=0表示都行 
int dp[maxn];
int sum[maxn];

int main(){
	scanf("%d%d%d",&n,&k1,&k2);
	for(int i=1;i<=k1;++i){
		int x;
		scanf("%d",&x);
		++x;
		p[x-1]=1;
		p[x]=-1;
	}
	for(int i=1;i<=k2;++i){
		int x;
		scanf("%d",&x);
		++x;
		p[x-1]=-1;
		p[x]=1;
	}
	
	sum[1]=dp[1]=1;
	for(int i=1;i<n;++i){
		for(int j=1;j<=i+1;++j){
			if(p[i]==-1){
				dp[j]=sum[j-1];
			}
			else if(p[i]==1){
				dp[j]=((sum[i]-sum[j-1])%mod+mod)%mod;
			}
			else{
				dp[j]=sum[i];
			}
		}
		for(int j=1;j<=i+1;++j){
			sum[j]=(sum[j-1]+dp[j])%mod;
		}
	}
	
	printf("%d\n",sum[n]);
	return 0;
}