AGC 005 D - ~K Perm Counting

D - ~K Perm Counting

链接

题意:

  求有多少排列对于每个位置i都满足$|ai−i|!=k$。n<=2000

分析:

  容斥+dp。

  $answer = \sum\limits_{i = 0}^{n}(-1)^ig[i] \times (n - i)!$

  $g[i]$表示至少存在I个位置满足$a[i] - i = k$个数。

  考虑如何求出$g[]$。 如果建立两列点,一个表示数字,一个表示下标,左边第i个点与右边第i-k和i+k个点连边,那么这是一张二分图,g[i]就是求满足有刚好i个匹配的方案数。

  发现这样图可以按照模k的余数分成2k条链,每条链互不影响,链内满足不能有相邻的一起选。于是可以$f[i][j][0]$表示到链上的第i个位置,当前有j个匹配,第i个选不选的方案数。

  考虑如何将2k条链合并:因为链与链之间是互不影响的,所以可以建立一个点,连接两条链,然后让这个点一定不选即可。

代码:

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>
#include<cmath>
#include<cctype>
#include<set>
#include<queue>
#include<vector>
#include<map>
using namespace std;
typedef long long LL;

inline int read() {
    int x=0,f=1;char ch=getchar();for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1;
    for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';return x*f;
}

const int N = 4005, mod = 924844033;
int f[N][N][2], g[N], A[N], fac[N];

inline void add(int &x,int y) { x += y; if (x >= mod) x -= y; }

int main() {
    int n = read(), k = read(), Ans = 0;
    int cnt = 0;
    for (int i = 1; i <= k; ++i) {
        for (int j = i; j <= n; j += k) A[++cnt] = (i == j);
        for (int j = i; j <= n; j += k) A[++cnt] = (i == j);
    }
    f[0][0][0] = 1;
    for (int i = 1; i <= (n << 1); ++i) 
        for (int j = 0; j <= i; ++j) {
            add(f[i][j][0], (f[i - 1][j][0] + f[i - 1][j][1]) % mod);
            if (!A[i] && j) add(f[i][j][1], f[i - 1][j - 1][0]);
        }
    fac[0] = 1;
    for (int i = 1; i <= n; ++i) fac[i] = 1ll * fac[i - 1] * i % mod;
    for (int i = 0; i <= n; ++i) {
        int res = (f[n << 1][i][0] + f[n << 1][i][1]) % mod;
        Ans += 1ll * (i & 1 ? -1 : 1) * res * fac[n - i] % mod;
        Ans = (Ans % mod + mod) % mod;
    }
    cout << Ans;
    return 0;
}

 

上一篇:全排列的多种写法


下一篇:BMS(电池管理系统)第六课 ——SOP&均衡 算法开发