codeforces 285 E. Positions in Permutations(容斥原理+dp)

题目链接

题目描述:一个 1 1 n的排列 p[i] p [ i ] ,一个位置是好位置当且仅当 |p[i]i|=1 | p [ i ] − i | = 1 ,求恰好有 k k 个好位置的排列数

分析:
首先我们可以选出k个位置,强制这 k k 个位置成为good position
其余的数任意排列,这样我们就知道 good position 数大于等于k的排列数了

我们首先考虑如何计算:选出i个位置,强制这 i i 个位置成为good position的序列数

先设计一个简单的dp:
f[i][j]表示到第 i i 位,有j个good position

注意:
这个状态只考虑good position,并不能构造合法的序列
也就是说我们只能计算出 j j 个good position的情况
最后答案是:f[n][j](nj)!(剩下的位置随意排列)

考虑第 i i 位能不能成为good position
那我们就要知道i1,i+1有没有使用过
显然 i+1 i + 1 是不可能使用过的
那么我们就需要记录一下 i1 i − 1 的使用情况:
f[i][j][0/1][0/1] f [ i ] [ j ] [ 0 / 1 ] [ 0 / 1 ] 表示到第 i i 位,有j个good position, i i 用没用过,i+1用没用过
f[i][j][a][b]>f[i+1] f [ i ] [ j ] [ a ] [ b ] − > f [ i + 1 ]

  • i+1 i + 1 位不选择 >f[i+1][j][b][0] − > f [ i + 1 ] [ j ] [ b ] [ 0 ]

  • i+1 i + 1 位填 i+2>f[i+1][j+1][b][1] i + 2 − > f [ i + 1 ] [ j + 1 ] [ b ] [ 1 ]

  • i+1 i + 1 位填 i i ,此时f[i][j][a][b] a a 必须等于0>f[i+1][j+1][b][0]

然而,这个dp得到的是good position大于等于k的序列

容斥原理?!
实际上我们在计算 f[n][j] f [ n ] [ j ] 的时候,是有大量重复的,那么到底重复了多少次呢?
C(j,k) C ( j , k ) 次!

ans= a n s =
f[n][k](nk)!C(k,k) f [ n ] [ k ] ∗ ( n − k ) ! ∗ C ( k , k )
f[n][k+1](n(k+1))!C(k+1,k) − f [ n ] [ k + 1 ] ∗ ( n − ( k + 1 ) ) ! ∗ C ( k + 1 , k )
+f[n][k+2](n(k+2))!C(k+2,k)... + f [ n ] [ k + 2 ] ∗ ( n − ( k + 2 ) ) ! ∗ C ( k + 2 , k ) . . .

tip

初始化: f[0][0][1][0]=1 f [ 0 ] [ 0 ] [ 1 ] [ 0 ] = 1

#include<cstdio>
#include<cstring>
#include<iostream>
#define ll long long

using namespace std;

const int N=1002;
const ll p=1e9+7;
ll c[N][N],f[N][N][2][2],jc[N];
int n,k;

void prepare() {
    jc[0]=1; jc[1]=1;
    for (int i=2;i<=n;i++) jc[i]=(jc[i-1]*(ll)i)%p;

    c[0][0]=1;
    for (int i=1;i<=n;i++) {
        c[i][0]=1;
        for (int j=1;j<=i;j++)
            c[i][j]=(c[i-1][j]+c[i-1][j-1])%p;
    }
}

void dp() {
    f[0][0][1][0]=1;     //第i位,已有j位GP,i位是否用了,i+1位是否用了
    for (int i=0;i<n;i++)
        for (int j=0;j<=i;j++) 
            for (int a=0;a<=1;a++)
                for (int b=0;b<=1;b++) 
                if (f[i][j][a][b]) {
                    f[i+1][j][b][0]+=f[i][j][a][b]; f[i+1][j][b][0]%=p;
                    f[i+1][j+1][b][1]+=f[i][j][a][b]; f[i+1][j+1][b][1]%=p;
                    if (!a)
                        f[i+1][j+1][b][0]+=f[i][j][a][b]; f[i+1][j+1][b][0]%=p;
                }
}

int main() {
    scanf("%d%d",&n,&k);
    prepare();
    dp();
    ll ans=0,o=1;
    for (int i=k;i<=n;i++) {
        ll t=0;
        t=(t+f[n][i][0][0]+f[n][i][1][0])%p;
        ans=(ans+((o*t%p*c[i][k]%p*jc[n-i])%p+p)%p);
        o*=-1;
    }
    printf("%I64d\n",(ans%p+p)%p);
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值