51Nod 1296 构造排列 + DP

该博客介绍了一道51Nod的题目,要求构造一个排列,使得某些元素大于左右邻居,其他元素小于左右邻居。通过动态规划(DP)的方法,博主详细阐述了如何构建状态转移方程,并给出了代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目链接


题意:
给定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[i1]的值来快速推出 d p [ i ] dp[i] dp[i]的值,因为当 j &lt; i j &lt; i j<i时,对于 d p [ i − 1 ] dp[i-1] dp[i1]数组中的合法排列, 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=1j1dp[i1][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=ji1dp[i1][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=1i1dp[i1][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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值