「2017 山东一轮集训 Day7」逆序对(生成函数)(容斥)(DP)

传送门

题意:长度为 n n n,有 k k k 个逆序对的排列个数?
n , k ≤ 1 e 5 n,k\le 1e5 n,k1e5

考虑新填一个数 i i i 进去,可能产生的贡献为 [ 0 , i − 1 ] [0,i-1] [0,i1],于是问题等价于:
∑ i = 1 n a i = k , a i ∈ [ 0 , i − 1 ] \sum_{i=1}^na_i=k,a_i\in[0,i-1] i=1nai=k,ai[0,i1] 的方案数
其实可以大力生成函数
F ( x ) = 1 ( 1 + x ) ( 1 + x + x 2 ) . . . ( 1 + x + x 2 + . . . + x n ) F(x)=1(1+x)(1+x+x^2)...(1+x+x^2+...+x^n) F(x)=1(1+x)(1+x+x2)...(1+x+x2+...+xn)
= ∏ i = 1 n ( 1 − x i ) ( 1 − x ) n =\frac{\prod_{i=1}^n(1-x^i)}{(1-x)^n} =(1x)ni=1n(1xi)
= ( ∏ i = 1 n ( 1 − x i ) ) ( ∑ j = 0 n ( j + n − 1 j ) x j ) =(\prod_{i=1}^n(1-x^i))(\sum_{j=0}^n\binom{j+n-1}{j}x^j) =(i=1n(1xi))(j=0n(jj+n1)xj)
考虑枚举后面的,需要快速知道前面的第 i ∈ [ 0 , k ] i\in[0,k] i[0,k]
考虑其组合意义,就是选若干个不重复的数,和为 i i i,贡献为 ( − 1 ) 个 数 (-1)^{个数} (1)
这里的个数显然是 k \sqrt k k 级别的,于是可以 d p dp dp
f i , j f_{i,j} fi,j 表示 i i i 个,和为 j j j 的方案,发现就是整体加若干个 1 填一个 1 再加整体加若干个 1
有一个坑点是当 j > n j>n j>n 时,可能有一个数被加成 n + 1 n+1 n+1,强制填 n + 1 n+1 n+1 容斥就可以了
复杂度 O ( k k ) O(k\sqrt k) O(kk )

#include<bits/stdc++.h>
#define cs const
using namespace std;
cs int M = 1000, N = 1e5 + 50;
cs int Mod = 1e9 + 7;
int add(int a, int b){ return a + b >= Mod ? a + b - Mod : a + b; }
int mul(int a, int b){ return 1ll * a * b % Mod; }
int dec(int a, int b){ return a - b < 0 ? a - b + Mod : a - b; }
int ksm(int a, int b){ int ans = 1; for(;b;b>>=1,a=mul(a,a)) if(b&1) ans=mul(ans,a); return ans; }
int sgn(int a){ return (a&1)?Mod-1:1; }
void Add(int &a, int b){ a = add(a, b); }
int n, m, k, f[M][N];
int fac[N<<1], ifac[N<<1];
int C(int n, int m){ if(n<0||m<0||n<m) return 0; return mul(fac[n],mul(ifac[m],ifac[n-m])); }
void prework(int n){
	fac[0] = fac[1] = ifac[0] = ifac[1] = 1;
	for(int i = 2; i <= n; i++) fac[i] = mul(fac[i-1], i);
	ifac[n] = ksm(fac[n], Mod-2);
	for(int i = n-1; i >= 2; i--) ifac[i] = mul(ifac[i+1], i+1);
}
int calc(int k){
	int ans = 0;
	for(int i = 0; i <= m; i++) Add(ans, mul(sgn(i),f[i][k]));
	return ans;
}
int main(){
	cin >> n >> k; prework(n + k + 5);
	m = sqrt(k * 2) + 5; f[0][0] = 1;
	for(int i = 1; i <= m; i++)
	for(int j = i; j <= k; j++){
		if(j >= i) f[i][j] = add(f[i][j-i], f[i-1][j-i]);
		if(j > n) f[i][j] = dec(f[i][j], f[i-1][j-n-1]);
	}
	int ans = 0;
	for(int i = 0; i <= k; i++){
		Add(ans, mul(C(i+n-1,i),calc(k-i)));
	} cout << ans; return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

FSYo

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值