题意:
给定N,要求构造满足要求的排列,对于其中的一些成员,值大于左右邻居,对于另一些成员,值小于左右邻居。输出满足条件的排列种数。
思路:
设
为前
个数构成的满足条件的合法排列,且末尾为
的个数。
那么对于第
个数,我们只需要考虑其与左邻居的关系(第i个数与右邻居的关系等价于第(i+1)个数与左邻居的关系)
构建一个数组
如果第
个数大于左邻居,则
如果第
个数小于左邻居,则
如果第
个数等于左邻居,则
另外还需要考虑的一个问题是,当我们在考虑前 个数组成的排列且末尾为 时,我们如何通过 的值来快速推出 的值,因为当 时,对于 数组中的合法排列, 是有可能已经出现在这些排列的中间,如果再在末尾插入 ,就出现了排列中有两个 的矛盾。
那应该如何来解决这个问题呢?此时有一个非常巧妙也非常经典的构建新排列的方式,当我们在一个排列的末尾插入 时,为避免冲突,我们将原排列的所有不小于 的数通通 ,这样的好处时,既不破坏原排列相邻数之间的大小关系,同时也避免了两个 出现的可能。
故此时就可以利用排列的构造方法来求得DP的状态转移方程了:
当
时
当
时
当
时
此题得解。
代码:
#include<cstdio>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
#include<string>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const int mod = 1e9 + 7;
const int A = 5000 + 10;
int dp[A],sum[2][A],a[A];
int main(){
int N,K,L;
scanf("%d%d%d",&N,&K,&L);
memset(a,-1,sizeof(a));
bool flag = 1;
for(int i=1 ;i<=K ;i++){
int x;scanf("%d",&x);
x++;
a[x] = 0;a[x+1] = 1;
}
for(int i=1 ;i<=L ;i++){
int x;scanf("%d",&x);
x++;
if(a[x] == 0 || a[x+1] == 1){
flag = 0;
break;
}
a[x] = 1;a[x+1] = 0;
}
if(flag == 0) puts("0");
else{
sum[0][0] = 1;
for(int i=1 ;i<=N ;i++){
sum[i&1][0] = 0;
for(int j=1 ;j<=i ;j++){
if(a[i] == -1) dp[j] = sum[(i&1)^1][i-1];
else if(a[i] == 1) dp[j] = sum[(i&1)^1][j-1];
else dp[j] = (sum[(i&1)^1][i-1] - sum[(i&1)^1][j-1])%mod;
sum[i&1][j] = (sum[i&1][j-1] + dp[j]) % mod;
}
}
ll ans = 0;
for(int i=1 ;i<=N ;i++){
ans = (ans + dp[i])%mod;
}
if(ans < 0) ans += mod;
printf("%I64d\n",ans);
}
return 0;
}