1. 程式人生 > 其它 >[2017 山東一輪集訓 Day7] 逆序對

[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);
}