题目描述
称一个
的排列的完美数为有多少个
满足
求有多少个长度为n 的完美数恰好为 m 的排列。
Sol
设
表示长度为
的排列恰好有
个完美数的方案数
但是发现不好算 , 我们先往容斥上想
设
表示至少有
个的方案
先考虑一个dp , 我们考虑当前位置上的数是不是要是幸运数
设 表示考虑到了第 位 , 至少已经有了 个幸运数 , 和 是否已经被用于充当幸运数 , 其他的位置不予考虑的方案数
为什么要这样设? 因为我们只考虑当前为就很好统计个数 , 然后当前位要成为幸运数就只能是 和 , 所以记录一下有没有用过
我们先自己 YY 一个转移(我这里卡常,数组换了维):
转移代码:
for(register int i=2;i<=n;++i){
for(register int j=0;j<i;++j){
upd(dp[0][0][i][j],dp[0][0][i-1][j]);
upd(dp[0][0][i][j],dp[1][0][i-1][j]);
upd(dp[1][0][i][j],dp[0][1][i-1][j]);
upd(dp[1][0][i][j],dp[1][1][i-1][j]);//不钦定当前位置
//钦定 i+1 过来
if(i<n){
upd(dp[0][1][i][j+1],dp[0][0][i-1][j]);
upd(dp[0][1][i][j+1],dp[1][0][i-1][j]);
upd(dp[1][1][i][j+1],dp[0][1][i-1][j]);
upd(dp[1][1][i][j+1],dp[1][1][i-1][j]);
}
//钦定了 i-1 过来
upd(dp[0][0][i][j+1],dp[0][0][i-1][j]);
upd(dp[1][0][i][j+1],dp[0][1][i-1][j]);
}
}
这个还是很好想的 , 最后由于不考虑的随便排列所以要乘上一个阶乘
于是答案是
??
显然我们多算了方案
假设我们的钦定法要确定
个是幸运数 , 那么可能我们决策时 钦定了 不同的
个,但是排列后却可能和之前已经钦定过
个的方案产生重复 , 从而把
对于要求的
来说 , 我们会算
次
,也就是任意选
个确定后 , 每次都包括了所有大于
个幸运数的方案
所以以
为容斥系数就行了
(其实我自己也很懵逼)
实在难以理解就二项式反演吧 , 上面的容斥其实就是二项式反演
具体来说 , 普通的二项式反演是这样的:
证明就是倒推…
和莫比乌斯反演一样 , 这种形式是做不了题的 , 有另外一种形式:
那么 表示那个恰好有 个幸运数的方案数
思考一下 的意义
由于我们是至少 , 很容易发现就是把多于
的都算了组合数把么多次 , 正好就是我们钦定法中算重的东西 , 于是就做完了 (神仙题)
代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<cstdlib>
#include<cmath>
#include<set>
using namespace std;
int n,m;
const int N=1001;
const int mod=1e9+7;
inline void upd(int &x,int y){x+=y;if(x>=mod) x-=mod;}
inline int sum(int x,int y){x+=y;if(x>=mod) x-=mod;return x;}
int dp[2][2][N][N];//前 i 个位置 至少有 j 个 最后一个位置 i和i+1 有没有已经被钦定 的方案数.... 瞎算算情况下的方案数
// 只考虑当前位置放数的时候产生的幸运数
int fac[N],inv[N];
inline int fpow(int x,int k)
{
register int ret=1;
while(k){
if(k&1) ret=1ll*ret*x%mod;
x=1ll*x*x%mod;k>>=1;
}
return ret;
}
inline int C(int i,int j){return 1ll*fac[i]*inv[j]%mod*inv[i-j]%mod;}
int main()
{
scanf("%d %d",&n,&m);
dp[0][0][1][0]=1;
dp[0][1][1][1]=1;
for(register int i=2;i<=n;++i){
for(register int j=0;j<i;++j){
upd(dp[0][0][i][j],dp[0][0][i-1][j]);
upd(dp[0][0][i][j],dp[1][0][i-1][j]);
upd(dp[1][0][i][j],dp[0][1][i-1][j]);
upd(dp[1][0][i][j],dp[1][1][i-1][j]);//不钦定当前位置
//钦定 i+1 过来
if(i<n){
upd(dp[0][1][i][j+1],dp[0][0][i-1][j]);
upd(dp[0][1][i][j+1],dp[1][0][i-1][j]);
upd(dp[1][1][i][j+1],dp[0][1][i-1][j]);
upd(dp[1][1][i][j+1],dp[1][1][i-1][j]);
}
//钦定了 i-1 过来
upd(dp[0][0][i][j+1],dp[0][0][i-1][j]);
upd(dp[1][0][i][j+1],dp[0][1][i-1][j]);
}
}
fac[0]=1;
for(register int i=1;i<=n;++i) fac[i]=1ll*fac[i-1]*i%mod;
inv[0]=1;inv[n]=fpow(fac[n],mod-2);
for(register int i=n-1;i;--i) inv[i]=(1ll*inv[i+1]*(i+1))%mod;
register int ans=0;
for(register int i=m,p=1;i<=n;++i,p=-p){//二项式反演
register int S=1ll*sum(dp[0][0][n][i],dp[1][0][n][i])*fac[n-i]%mod;
ans+=(1ll*S*C(i,m)%mod*p+mod)%mod;
if(ans>=mod) ans-=mod;
}
printf("%d\n",ans);
}