[2017 山东一轮集训 Day7] 逆序对
一、题目
这么简单的去重我竟然没想到,我是个哈批。
二、解法
首先有一个显然的 \(dp\),依次加入 \(1\) 到 \(i\),每次考虑逆序对的增量:
\[dp[i][j+k]\leftarrow dp[i-1][j] \ \ \ k\in[0,i)
\]
这个可以用前缀和优化,时间复杂度 \(O(n^2)\),可以写成生成函数的形式:
\[\prod_{i=1}^n\sum_{j=0}^ix^j=\prod_{i=1}^n\frac{1-x^{i}}{1-x}
\]
分母并不需要多项式求逆,可以直接最后隔板法组合意义算,问题是 \(\prod 1-x^i\) 的计算,这个算式从容斥的角度也可以解释,也就是钦定一个位置的逆序对不合法就会带来 \(-1\) 的容斥系数。
因为我多项式学得太差了所以只会 \(dp\),考虑钦定位置的个数要 \(\leq\sqrt {2k}\) 才有可能有方案,发现这个东西就是柱状图 \(dp\) 板子,考虑有若干个柱子,每次可以新增一个柱子或者把所有柱子增加 \(1\) 的高度,那么转移,设 \(dp[i][j]\) 表示有 \(i\) 个柱子,柱子的总高度是 \(j\),我们从小到大枚举 \(j\):
\[dp[i][j]\leftarrow dp[i-1][j-i]+dp[i][j-i]
\]
但是会算重,因为如果出现高度为 \(n+1\) 的柱子就不合法,那么直接减去这种方案即可:
\[dp[i][j]\leftarrow dp[i][j]-dp[i-1][j-n-1]
\]
设 \(f(t)\) 为不定方程 \(x_1+x_2..+x_n=t\) 的不定整数解个数,那么答案式长这样:
\[\sum_i\sum_j(-1)^i\cdot dp[i][k-j]\cdot f(j)
\]
时间复杂度 \(O(n\sqrt n)\)
#include <cstdio>
#include <cmath>
const int M = 100005;
const int MOD = 1e9+7;
#define int long long
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int n,m,k,dp[500][M],fac[2*M],inv[2*M],ans;
void init(int n)
{
fac[0]=inv[0]=inv[1]=1;
for(int i=1;i<=n;i++) fac[i]=fac[i-1]*i%MOD;
for(int i=2;i<=n;i++) inv[i]=inv[MOD%i]*(MOD-MOD/i)%MOD;
for(int i=2;i<=n;i++) inv[i]=inv[i-1]*inv[i]%MOD;
}
int C(int n,int m)
{
if(n<m || m<0) return 0;
return fac[n]*inv[m]%MOD*inv[n-m]%MOD;
}
int cal(int x)
{
return C(x+n-1,n-1);
}
signed main()
{
//freopen("perm.in","r",stdin);
//freopen("perm.out","w",stdout);
n=read();k=read();init(2e5);
dp[0][0]=1;m=499;
for(int i=1;i<=m;i++)
{
for(int j=i;j<=k;j++)
{
dp[i][j]=(dp[i-1][j-i]+dp[i][j-i])%MOD;
if(j>=n+1) dp[i][j]=(dp[i][j]-dp[i-1][j-n-1])%MOD;
}
}
for(int i=0;i<=m;i++)
for(int j=0;j<=k;j++)
{
int f=(i%2?-1:1);
ans=(ans+1ll*f*dp[i][k-j]*cal(j))%MOD;
}
printf("%lld\n",(ans+MOD)%MOD);
}