题意:
给定N,要求构造满足要求的排列,对于其中的一些成员,值大于左右邻居,对于另一些成员,值小于左右邻居。输出满足条件的排列种数。
思路:
设
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j]为前
i
i
i 个数构成的满足条件的合法排列,且末尾为
j
j
j的个数。
那么对于第
i
i
i个数,我们只需要考虑其与左邻居的关系(第i个数与右邻居的关系等价于第(i+1)个数与左邻居的关系)
构建一个数组
a
a
a
如果第
i
i
i个数大于左邻居,则
a
[
i
]
=
1
a[i] = 1
a[i]=1
如果第
i
i
i个数小于左邻居,则
a
[
i
]
=
0
a[i] = 0
a[i]=0
如果第
i
i
i个数等于左邻居,则
a
[
i
]
=
−
1
a[i] = -1
a[i]=−1
另外还需要考虑的一个问题是,当我们在考虑前 i i i个数组成的排列且末尾为 j j j时,我们如何通过 d p [ i − 1 ] dp[i-1] dp[i−1]的值来快速推出 d p [ i ] dp[i] dp[i]的值,因为当 j < i j < i j<i时,对于 d p [ i − 1 ] dp[i-1] dp[i−1]数组中的合法排列, j j j是有可能已经出现在这些排列的中间,如果再在末尾插入 j j j,就出现了排列中有两个 j j j的矛盾。
那应该如何来解决这个问题呢?此时有一个非常巧妙也非常经典的构建新排列的方式,当我们在一个排列的末尾插入 j j j时,为避免冲突,我们将原排列的所有不小于 j j j的数通通 + 1 +1 +1,这样的好处时,既不破坏原排列相邻数之间的大小关系,同时也避免了两个 j j j出现的可能。
故此时就可以利用排列的构造方法来求得DP的状态转移方程了:
当
a
[
i
]
=
1
a[i] = 1
a[i]=1时
d
p
[
i
]
[
j
]
=
∑
k
=
1
j
−
1
d
p
[
i
−
1
]
[
k
]
dp[i][j] = \sum_{k=1}^{j-1}dp[i-1][k]
dp[i][j]=∑k=1j−1dp[i−1][k]
当
a
[
i
]
=
0
a[i] = 0
a[i]=0时
d
p
[
i
]
[
j
]
=
∑
k
=
j
i
−
1
d
p
[
i
−
1
]
[
k
]
dp[i][j] = \sum_{k=j}^{i-1}dp[i-1][k]
dp[i][j]=∑k=ji−1dp[i−1][k]
当
a
[
i
]
=
−
1
a[i] = -1
a[i]=−1时
d
p
[
i
]
[
j
]
=
∑
k
=
1
i
−
1
d
p
[
i
−
1
]
[
k
]
dp[i][j] = \sum_{k=1}^{i-1}dp[i-1][k]
dp[i][j]=∑k=1i−1dp[i−1][k]
此题得解。
代码:
#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;
}
该博客介绍了一道51Nod的题目,要求构造一个排列,使得某些元素大于左右邻居,其他元素小于左右邻居。通过动态规划(DP)的方法,博主详细阐述了如何构建状态转移方程,并给出了代码实现。
6883

被折叠的 条评论
为什么被折叠?



