Codeforces Round #717 (Div. 2) E. Baby Ehab Plays with Permutations 第一类斯特林数,容斥,DP
题意
给定一个长度为(n)的排列(初始就是1到n),每次操作可以交换两个数,(k)次操作后能得到多少种排列
答案取余(1e9 + 7)
分析
(Sol1):
先给一个相对来说比较intuitive的做法,奈何对这块理解不深。注意到答案就是第一类斯特林数的后(k)项的偶数项的和,由于(k)较小(n)较大,并且有性质(S_1(n,n-k))是关于(n)的(2k+1)次多项式
于是对(S_1)进行若干次拉格朗日插值即可
(Sol2:)
以下是官方题解做法。
我们可以认为对排列做(k)次操作计数相当于这样一个问题:
有多少种排列经过(k)次操作可以使其有序。用类似解决错排的思想,考虑新加入一个数
(dp[n][j])表示前(n)个数恰好经过(j)次有序的贪心方案数(每次都去交换到正确位置),那么第(n)个数是(n),可以不用操作,否则和前面的(n)交换,这里(n)有(n - 1)种可能
因此(dp[n][j] = dp[n - 1][j] + (n - 1)dp[n - 1][j - 1])
注意到这是反着做且贪心的情况,事实上根本不需要每次都交换到正确位置,比如可以对同样一对位置交换两次,这相当于将一个合并的环又拆开
容易发现我们要求的实际上是(dp[n][j] + dp[n][j - 2] + dp[n][j - 4]...)
然而(n)太大了,我们需要从(k)的角度出发考虑此题。
容易发现最终最多影响(2 k)个位置,这给了我们发挥空间
答案是否是 (sum_{j=0}^{k}sum_{i=0}^j binom{n}{j} dp[j][i]) 这么做显然会带来重复计算
令(newdp[n][j])表示前(n)个数交换(j)次恰好有序,且没有一个数在自己原本的位置的方案数,
其容斥思想完全可以仿照错排问题的容斥做法
回顾一下错排问题
可以转化为求并集(满足存在(P_i = i)的排列的数量 )
套用容斥的公式展开就变成了求(k)个数的交集的大小,这(k)个数满足(P_i = i),那么剩下的数可以随意排列
因此有
到了此题,计算(newdp[n][j])可以先转化为补集(从(dp[n][j])中去减),于是变为了求满足有在原来的位置的并集
套用容斥就变为了求(k)个位置的交集,这(k)个位置且其他随意排列的方案其实就是(dp[n-k][j])
于是
与是对答案(newdp)乘个组合数然后累加偶数次前缀和即可
code1
#include <bits/stdc++.h>
const int mod = 1e9 + 7;
using ll = long long;
int inv(int x) { return x == 1 ? 1 : (ll) ( mod - mod / x ) * inv(mod % x) % mod; }
int x[1005],y[1005],f[1005],n,k,S1[1005][1005];
int lagrange(int n,int k) {
int ans = 0;
for (int i = 1; i <= n; ++i) {
//printf("<%d %d>
",x[i],y[i]);
ll sum=1, sum2=1;
for (int j = 1; j <= n; ++j) {
if (j == i) continue;
sum = sum*(k - x[j] + mod) % mod;
sum2 = sum2 * (x[i] - x[j] + mod) % mod;
} ans = (ans + y[i] * sum % mod * inv(sum2) % mod) % mod;
} //printf("%d
",ans);
return ans;
}
int calc(int n,int r) {
int N = 0;
for (int i = r; i <= r + 2 * k + 3; ++ i) {
y[++N] = S1[i][i - r];
x[N] = i;
}
return lagrange(N,n);
}
int main() {
scanf("%d%d",&n,&k);
S1[0][0] = 1;
for (int i = 1; i <= 1000; ++ i)
for (int j = 1; j <= i; ++ j)
S1[i][j] = ( (ll) (i - 1) * S1[i-1][j] % mod + S1[i-1][j-1] ) % mod;
for (int i = 0; i <= k; ++ i) {
f[i] = calc(n,i);
// printf("%d ",f[i]);
}
for (int i = 1; i <= k; ++ i) {
int ans = 0;
for (int j = i; j >= 0; j -= 2) ans = (ans + f[j]) % mod;
printf("%d ",ans);
}
return 0;
}
code2
#include <bits/stdc++.h>
using namespace std;
#define mod 1000000007
long long ncr[405][405],inv[405],dp[405][405];
long long big_ncr(int n,int r)
{
long long ret=1;
for (int i=n-r+1;i<=n;i++)
ret=(ret*i)%mod;
for (int i=1;i<=r;i++)
ret=(ret*inv[i])%mod;
return ret;
}
int main()
{
int n,k;
scanf("%d%d",&n,&k);
ncr[0][0]=1;
for (int i=1;i<=2*k;i++)
{
dp[i][0]=1;
ncr[i][0]=1;
for (int j=1;j<=2*k;j++)
{
dp[i][j]=(dp[i-1][j]+(i-1)*dp[i-1][j-1])%mod;
ncr[i][j]=(ncr[i-1][j]+ncr[i-1][j-1])%mod;
}
}
inv[1]=1;
for (int i=2;i<=2*k;i++)
inv[i]=inv[mod%i]*(mod-mod/i)%mod;
int ans[]={1,0};
for (int j=1;j<=k;j++)
{
for (int i=1;i<=min(n,2*j);i++)
{
int cnt=0;
for (int f=0;f<=i;f++)
cnt=(cnt+(f%2? -1:1)*ncr[i][f]*dp[i-f][j]%mod+mod)%mod;
ans[j%2]=(ans[j%2]+big_ncr(n,i)*cnt)%mod;
}
printf("%d ",ans[j%2]);
}
}