[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);
}
posted @ 2021-08-09 11:47  C202044zxy  阅读(226)  评论(0编辑  收藏  举报