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

【题目描述】
在这里插入图片描述

【思路】
做这道题首先要知道一种全排列的生成方式:如果要生成 [1,n][1,n][1,n] 的全排列,考虑递推关系,如果现在所有 [1,n−1][1,n-1][1,n1] 的排列都是已知的,那么假设 [1,n][1,n][1,n] 中的一个排列末尾元素为 xxx ,那么只要把 [1,n−1][1,n-1][1,n1] 中所有 >=x>=x>=x 的元素都 +1+1+1,然后把 xxx 放在第 nnn 项,就能构造出一个 [1,n][1,n][1,n] 的排列,并且前面的 n−1n-1n1 项之间相互大小关系是不变的

首先根据输入数据处理出一个数组 ppp ,表示相邻两项的关系,p[i]==1p[i]==1p[i]==1 表示 a[i]&gt;a[i+1]a[i]&gt;a[i+1]a[i]>a[i+1]p[i]==−1p[i]==-1p[i]==1 表示 a[i]&lt;a[i+1]a[i]&lt;a[i+1]a[i]<a[i+1]p[i]==0p[i]==0p[i]==0 表示无限制,然后

dp[i][j]dp[i][j]dp[i][j] 表示 数字 [1,i][1,i][1,i] 在前 iii 个位置,以 jjj 为结尾生成的合法排列数目,有如下状态转移方程

dp[i+1][j]={∑k=1j−1dp[i][k]   (p[i]==−1)∑k=jidp[i][k]   (p[i]==1)∑k=1idp[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}dp[i+1][j]=k=1j1dp[i][k]   (p[i]==1)k=jidp[i][k]   (p[i]==1)k=1idp[i][k]   (p[i]==0) 递推边界从 dp[1][1]=1dp[1][1]=1dp[1][1]=1 开始,很明显可以通过前缀和来优化这个递推方程,在递推过程中用一个 sumsumsum 数组记录前缀和 sum[j]=∑k=1jdp[i][j]sum[j]=\sum_{k=1}^{j}dp[i][j]sum[j]=k=1jdp[i][j],每次用 sumsumsum 递推,然后不断更新 sumsumsum

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

可以看到上面的代码中 dp[i][j]dp[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;
}

转载于:https://www.cnblogs.com/wafish/p/10465116.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值