ATG5D K Perm Counting 学习笔记

ATG5D K Perm Counting 学习笔记

Luogu Link

题意简述

给定 \(n,k\),问有多少长度为 \(n\) 的排列 \(P\) 满足,\(\forall i\in[1,n]\)\(|P_i-i|\neq k\)

\(1\le k<n\le 2\times 10^3\)。答案对 \(924844033\) 取模。

$ $。

做法解析

如果你看过一些关于组合数学的书,你可能会想到,这种排列计数可以对应到这样的图上:

pVQfcZQ.md.png

所求的排列个数,就等于在这样的棋盘格内放 \(n\) 个互不攻击的车,且阴影格子不能放的方案数。

“注意到”这一块,这是怎么想到的呢?但反正它就是能这么转化。
实际上你不怎么转化,这题也不是不能做。但这么转化一下是最直观的。

但是,“且阴影格子不能放”和“互不攻击”一起直接处理似乎做不到。怎么办呢?

这个时候就可以容斥了,答案就等于 \(\sum_{i=0}^{n}(-1)^if_i(n-i)!\),其中 \(f_i\) 表示强制填 \(i\) 个东西到阴影格子里(其它的另外随便填)的方案数。

\(f_i\) 怎么求呢?你把不能同时选的阴影格子连上边,你知道 \(f_i\) 实际上就是在求大小 \(i\) 独立集的个数。你发现它们连成了多条链,而对于每条链来说,若其包含 \(x\) 个点,要选出 \(j\) 个互不相连的点,方案数即为 \(\binom{x+1-m}{m}\)。你暴力卷积就做完了。

pVQ4U3t.md.png

你在写代码时反而会发现相对最难的一步是如何确定每条链的长度。实际上每条链(在无视格子限制无限延长的情况下)可以被其经过的(可能合法的)一个形如 \((i,i+k)\) 的点标识。你先判断一下这样一条链能否从右上角或左下角开始,然后你去考虑一下它往两个方向分别可以延长多少。注意一些边界情况。

啊然后这玩意甚至复杂度还是对的。\(O(n^2)\) 级别。

代码实现

#include <bits/stdc++.h>
using namespace std;
using namespace obasic;
const int Mod=924844033;
using namespace omodint;
using mint=modint<Mod>;
const int MaxN=2e3+5;
mint facr[MaxN],finv[MaxN];
using namespace omathe;
int N,K,vis[MaxN];
mint S[MaxN],T[MaxN],ans;
int main(){
    readis(N,K);premwork(N);
    S[0]=1;
    for(int d=1,len;d<=min(N,2*K);d++){
        len=(d-K>=1)+(d+K<=N);
        len+=(N-d)/(2*K)+(N-d-K)/(2*K);
        for(int j=0;j<=N;j++)T[j]=S[j],S[j]=0;
        for(int j=0;j<=pcedi(len,2);j++){
            mint tmp=Comb(len+1-j,j);
            for(int i=0;i+j<=N;i++)S[i+j]+=T[i]*tmp;
        }
    }
    for(int i=0;i<=N;i++)ans+=S[i]*facr[N-i]*(i&1?-1:1);
    writi(miti(ans));
    return 0;
}
posted @ 2025-07-11 10:19  矞龙OrinLoong  阅读(8)  评论(0)    收藏  举报