LOJ 6077「2017 山东一轮集训 Day7」逆序对
加入第 \(i\) 个数字的时候,新增的逆序对个数为 \([0,i-1]\),于是题意转化为:求方程 \(\sum \limits _{i=1} ^n x_i=k\ (1\leq x_i\leq i)\) 解的个数。
当没有 \((x_i\leq i)\) 的限制时,解的个数为 \(\binom {k+n-1} {n-1}\),可以使用隔板法计算。
考虑使用容斥去掉限制。我们钦点其中若干个 \(x_i\) 不满足限制,即 \(x_i>i\),剩下的限制为 \(x_i>0\),可以将 \(k\) 减去 \(i\) 使得对于所有的 \(x\) 限制均为 \(x_i>0\)。枚举 \(i\) 个限制不满足,这些位置为 \(p_1,p_2\cdots p_n\),那么答案可以写成:
\[\sum _ {i=1} ^ {n} (-1)^i\sum _{p_1<p_2<\cdots<p_i} \binom {k+n-1-\sum _{j=1}^ip_j} {n-1}
\]
发现后面的式子对答案的贡献只和 \(\sum p_i\) 有关,和 \(p\) 具体是什么数没有关系。于是我们可以考虑枚举 \(s=\sum p_i\) ,求出 \(f_{i,s}\) 表示从 \([1,n]\) 中选出 \(i\) 个不同的数,和为 \(s\) 的方案数。
这是经典的整数划分问题。假设当前序列中有 \(i\) 个数,和为 \(j\) ,可以执行以下操作:
- 将序列所有数都加 \(1\)。
- 将序列所有数都加 \(1\),并在序列最后加一个 \(1\)。
可以证明,这样操作和每个整数划分的方案一一对应。
但是这样还有一个问题,序列中的数有可能会大于 \(n\)。容易发现这个大于 \(n\) 的数只有一个且必定为 \(n+1\)。这种方案数必定和去掉 \(n+1\) 后的合法序列方案数相同。
可以写出 \(dp\) 转移方程:
\[f[i][j]=f[i][j-i]+f[i-1][j-i]-f[i-1][j-n-1]
\]
带上容斥系数就是:
\[f[i][j]=f[i][j-i]-f[i-1][j-i]+f[i-1][j-n-1]
\]
注意到 \(i\) 是 \(O(\sqrt k)\) 级别的,总复杂度为 \(O(n\sqrt k)\)。
代码
#include <bits/stdc++.h>
using namespace std;
#define N 200010
#define Mod 1000000007
inline int read() {
int x=0;
char ch=getchar();
while (!isdigit(ch)) ch=getchar();
while (isdigit(ch)) x=x*10+ch-'0',ch=getchar();
return x;
}
int fac[N],inv[N];
inline void init(int n) {
fac[0]=fac[1]=inv[0]=inv[1]=1;
for (int i=1;i<=n;i++) fac[i]=1LL*fac[i-1]*i%Mod;
for (int i=2;i<=n;i++) inv[i]=1LL*(Mod-Mod/i)*inv[Mod%i]%Mod;
for (int i=2;i<=n;i++) inv[i]=1LL*inv[i-1]*inv[i]%Mod;
}
inline int C(int n,int m) {
return 1LL*fac[n]*inv[m]%Mod*inv[n-m]%Mod;
}
int dp[510][N],f[N];
int main() {
int n=read(),k=read(),ans=0; init(n+k);
int m=min(n,(int)sqrt(k<<1)+1); dp[0][0]=f[0]=1;
for (int i=1;i<=m;i++)
for (int j=i*(i+1)>>1;j<=k;j++) {
(dp[i][j]+=dp[i][j-i])%=Mod;
(dp[i][j]+=Mod-dp[i-1][j-i])%=Mod;
if (j>=n+1) (dp[i][j]+=dp[i-1][j-n-1])%=Mod;
(f[j]+=dp[i][j])%=Mod;
}
for (int i=0;i<=k;i++) (ans+=1LL*f[i]*C(k-i+n-1,n-1)%Mod)%=Mod;
return printf("%d\n",ans),0;
}

浙公网安备 33010602011771号