JZOJ 5253. 排列与交换

Description

一个数组A = [1, 2, 3, …, n]。

对A进行好恰好k次相邻交换,能得到多少个不同的序列 (S1)?
对A进行最多k次交换,你能得到多少个不同的序列 (S2)?

一次相邻交换是指交换数组A中两个相邻位置的元素,即:交换A[i]和A[i+1]或者A[i]和A[i-1]。
一次交换是指交换数组A中的任意两个位置不同的元素,即:交换A[i]和A[j],1 <= i, j <= N, i != j。

给出数组A的长度N,以及次数K,求S1和S2。由于结果很大,输出Mod 1000000007的结果。
例如:原始数组: [1, 2, 3]

  1. 经过两次相邻交换:
    我们得到 [1, 2, 3], [2, 3, 1], [3, 1, 2] ==> S1 = 3

  2. 经过最多两次交换:
    1) 0次交换后: [1, 2, 3]
    2) 1次交换后: [2, 1, 3], [3, 2, 1], [1, 3, 2].
    3) 两次交换后: [1, 2, 3], [2, 3, 1], [3, 1, 2] ==> S2 = 6

Input

数组A的长度N,以及次数K。

Output

S1和S2。由于结果很大,输出Mod 1000000007的结果。

Sample Input

5 10

Sample Output

60 120

Data Constraint

对于50%数据N,K<=500
对于100%数据1<=N, K<=3000

Solution

  • 问题分为两个子问题:恰好 k 次相邻交换的排列数、最多 k 次交换排列数。

  • 对于第一个子问题,设 F[i][j] 表示做到第 i 个数,交换次数为 j 能得到的排列数。

  • 首先继承上一个

    F[i][j]=f[i1][j]

  • 不过当 i 交换到第 1 个位置之后,再换就会算重,所以:

    F[i][i+j]=F[i1][j]

  • 最后:

    F[i][j]+=F[i][j1]

  • (这里是不断向前交换)。

  • 这样最后的答案就是 F[n][i] (ki) ,即剩下 ki 步可以不断的交换相邻两个来达到。

  • 对于第二个子问题,就相对比较简单了。

  • 同样设 F[i][j] 表示做到第 i 个数,交换次数为 j 能得到的排列数。

  • 那么则有:

    F[i][j]=F[i1][j]+F[i1][j1](i1)

  • 因为对于第 i 个数无非就是不交换或者与前 i1 个数交换。

  • 显然答案即为:

    i=0kF[n][i]

  • 这样两个 DP 的时间复杂度都是 O(NK) ,则总复杂度为 O(NK) 可过本题。

Code

#include<cstdio>
#include<cstring>
using namespace std;
const int mo=1e9+7;
int n,k,roll,ans;
int f[2][3002];
int main()
{
    scanf("%d%d",&n,&k);
    for(int i=f[0][0]=1;i<=n;i++)
    {
        roll^=1;
        memset(f[roll],0,sizeof(f[roll]));
        for(int j=0;j<=k;j++)
        {
            f[roll][j]=((long long)f[roll][j]+f[roll^1][j]+f[roll][j-1])%mo;
            if(i+j<=k) f[roll][i+j]=(f[roll][i+j]+mo-f[roll^1][j])%mo;
        }
    }
    for(int i=0;i<=k;i++)
        if(!(k-i&1)) ans=(ans+f[roll][i])%mo;
    printf("%d ",ans);
    memset(f[0],ans=roll=0,sizeof(f[0]));
    for(int i=1;i<=n;i++)
    {
        roll^=1;
        memset(f[roll],0,sizeof(f[roll]));
        f[roll][0]=1;
        for(int j=1;j<=k;j++)
            f[roll][j]=(f[roll^1][j]+(long long)f[roll^1][j-1]*(i-1))%mo;
    }
    for(int i=0;i<=k;i++) ans=(ans+f[roll][i])%mo;
    printf("%d",ans);
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值