LOJ6077「2017 山东一轮集训 Day7」逆序对 (生成函数+多项式exp?朴素DP!)

89 篇文章 0 订阅
16 篇文章 0 订阅

题面

给定 n , k n,k n,k ,求长度为 n n n 逆序对个数为 k k k 的排列个数,对 1 e 9 + 7 \rm1e9+7 1e9+7 取模。

1 ≤ n , k ≤ 100   000 1\leq n,k\leq 100\,000 1n,k100000

题解

首先,不要看到逆序对就手忙脚乱,它其实是可控的。

d i d_i di 为第 i i i 个数前面比它大的数的个数,满足条件 d i ∈ [ 0 , i ) d_i\in[0,i) di[0,i) 。一个 d d d 序列,实际上只需要满足这个条件,他就能够成立,并且唯一对应一个排列。这个在POJ3761 Bubble Sort中证明过,可以通过 d d d 序列构造出排列来。

然后,就是个比较经典的问题: n n n 个非负整数,各有上限,求总和为 k k k 的方案数。这题的特殊之处在于,每个数的上界依次是 0 0 0 n − 1 n-1 n1

先设 f ( x ) f(x) f(x) 为这 n n n 个非负整数没有上限的情况下,总和为 x x x 的方案数。经验告诉我们,利用隔板法,可得 f ( x ) = C ( x + n − 1 , x ) f(x)=C(x+n-1,x) f(x)=C(x+n1,x)

生成函数

我们可以用生成函数来做,走起!

可以得到第 i i i 个数的生成函数为
1 + x + x 2 + . . . + x i − 1 = ( ∑ j = 0 ∞ x j ) − ( ∑ j = i ∞ x j ) = 1 − x i x − 1 1+x+x^2+...+x^{i-1}=(\sum_{j=0}^{\infty}x^j)-(\sum_{j=i}^{\infty}x^j)=\frac{1-x^i}{x-1} 1+x+x2+...+xi1=(j=0xj)(j=ixj)=x11xi

(我们用 [ [ x i ] ] F ( x ) [[x^i]]F(x) [[xi]]F(x) 来表示函数 F ( x ) F(x) F(x) i i i 次项系数)于是答案可表示为
[ [ x k ] ] ∏ i = 1 n 1 − x i x − 1 = [ [ x k ] ] ( ( 1 x − 1 ) n ∏ i = 0 n ( 1 − x i ) ) = [ [ x k ] ] ( ( ∑ i = 0 ∞ x i ) n ∏ i = 0 n ( 1 − x i ) ) = ∑ j = 0 k [ [ x j ] ] ( ∑ i = 0 ∞ x i ) n ⋅ [ [ x k − j ] ] ( ∏ i = 0 n ( 1 − x i ) ) [[x^k]]\prod_{i=1}^{n}\frac{1-x^i}{x-1}=[[x^k]]\Big((\frac{1}{x-1})^n\prod_{i=0}^{n}(1-x^i)\Big)\\ =[[x^k]]\Big((\sum_{i=0}^{\infty}x^i)^n\prod_{i=0}^n(1-x^i)\Big)\\ =\sum_{j=0}^{k}[[x^j]](\sum_{i=0}^{\infty}x^i)^n\cdot[[x^{k-j}]]\Big(\prod_{i=0}^n(1-x^i)\Big) [[xk]]i=1nx11xi=[[xk]]((x11)ni=0n(1xi))=[[xk]]((i=0xi)ni=0n(1xi))=j=0k[[xj]](i=0xi)n[[xkj]](i=0n(1xi))

这个东西: [ [ x j ] ] ( ∑ i = 0 ∞ x i ) n [[x^j]](\sum_{i=0}^{\infty}x^i)^n [[xj]](i=0xi)n ,不就是无限制的 n n n 个自然数的情况吗?不就是 f ( j ) f(j) f(j)
= ∑ j = 0 k f ( j ) ⋅ [ [ x k − j ] ] ( ∏ i = 0 n ( 1 − x i ) ) =\sum_{j=0}^{k}f(j)\cdot[[x^{k-j}]]\Big(\prod_{i=0}^n(1-x^i)\Big) =j=0kf(j)[[xkj]](i=0n(1xi))

接下来,我们要对这个函数 ∏ i = 0 n ( 1 − x i ) \prod_{i=0}^n(1-x^i) i=0n(1xi) 开膛破肚了。

先取对数转化成加法:
exp ⁡ ( ∑ i = 0 n ln ⁡ ( 1 − x i ) ) \exp\Big( \sum_{i=0}^n\ln(1-x^i) \Big) exp(i=0nln(1xi))

然后,我们手推二项式 ln ⁡ \ln ln ,运用链式法则求导再求积分:
exp ⁡ ( ∑ i = 0 n ln ⁡ ( 1 − x i ) ) = exp ⁡ ( ∑ i = 0 n ∫ ( ln ⁡ ′ ( 1 − x i ) ⋅ ( 1 − x i ) ′ ) d x ) = exp ⁡ ( ∑ i = 0 n ∫ − i x i − 1 1 − x i d x ) \exp\Big( \sum_{i=0}^n\ln(1-x^i) \Big)=\exp\Big( \sum_{i=0}^n\int(\ln'(1-x^i)\cdot(1-x^i)'){\rm dx} \Big)\\ =\exp\Big( \sum_{i=0}^n\int\frac{-ix^{i-1}}{1-x^i}{\rm dx} \Big) exp(i=0nln(1xi))=exp(i=0n(ln(1xi)(1xi))dx)=exp(i=0n1xiixi1dx)

再手推 g ( x ) = 1 − x i g(x)=1-x^i g(x)=1xi 取模 x k + 1 x^{k+1} xk+1 下的逆元。不难发现 ( 1 − x i ) ( 1 + x i + x 2 i + x 3 i + . . . ) = 1 − x i + x i − x 2 i + x 2 i − . . . ≡ 1 (1-x^i)(1+x^i+x^{2i}+x^{3i}+...)=1-x^i+x^i-x^{2i}+x^{2i}-...\equiv1 (1xi)(1+xi+x2i+x3i+...)=1xi+xix2i+x2i...1 ,因此它的逆元是 h ( x ) = 1 + x i + x 2 i + x 3 i + . . . h(x)=1+x^i+x^{2i}+x^{3i}+... h(x)=1+xi+x2i+x3i+... 。代入进去:
exp ⁡ ( ∑ i = 0 n ∫ − i x i − 1 1 − x i d x ) = exp ⁡ ( ∑ i = 0 n ∫ − i x i − 1 ( ∑ j = 0 ∞ x i j ) d x ) = exp ⁡ ( ∑ i = 0 n ∫ ( ∑ j = 1 ∞ − i x i j − 1 ) d x ) = exp ⁡ ( ∑ i = 0 n ( ∑ j = 1 ∞ − i x i j i j ) ) = exp ⁡ ( ∑ i = 1 k − ( ∑ j ∣ i n j ) x i ) \exp\Big( \sum_{i=0}^n\int\frac{-ix^{i-1}}{1-x^i}{\rm dx} \Big)=\exp\Big( \sum_{i=0}^n\int-ix^{i-1}(\sum_{j=0}^{\infty}x^{ij}){\rm dx} \Big)\\ =\exp\Big( \sum_{i=0}^n\int(\sum_{j=1}^{\infty}-ix^{ij-1}){\rm dx} \Big)\\ =\exp\Big( \sum_{i=0}^n(\sum_{j=1}^{\infty}-i\frac{x^{ij}}{ij}) \Big)\\ =\exp\Big( \sum_{i=1}^{k} -(\sum_{j|i}^nj)x^i \Big) exp(i=0n1xiixi1dx)=exp(i=0nixi1(j=0xij)dx)=exp(i=0n(j=1ixij1)dx)=exp(i=0n(j=1iijxij))=exp(i=1k(jinj)xi)

中间的 ∑ j ∣ i n j \sum_{j|i}^nj jinj 其实就是 i i i 小于等于 n n n 的因数和,可以用埃筛预处理出来。然后再用多项式exp就可以求出这个多项式了,令该多项式的 i i i 次项系数为 G [ i ] G[i] G[i] ,那么答案就是
∑ j = 0 k C ( j + n − 1 , j ) ⋅ G [ k − j ] \sum_{j=0}^{k}C(j+n-1,j)\cdot G[k-j] j=0kC(j+n1,j)G[kj]

预处理阶乘求组合数就完了。时间复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn) 。还得写任意模数NTT之类。

动态规划

但是多项式exp的常数有好几十,其实不优,很难跑过 1s。

我们其实可以用容斥,枚举超过上限的个数,答案转化过来,其实也是上面的一个式子:
∑ j = 0 k f ( j ) ⋅ [ [ x k − j ] ] ( ∏ i = 0 n ( 1 − x i ) ) \sum_{j=0}^{k}f(j)\cdot[[x^{k-j}]]\Big(\prod_{i=0}^n(1-x^i)\Big) j=0kf(j)[[xkj]](i=0n(1xi))

不熟悉生成函数的可能有点晕,但是熟知 D P \rm DP DP 的我们应该可以意识到,右边的 [ [ x k − j ] ] ( ∏ i = 0 n ( 1 − x i ) ) [[x^{k-j}]]\Big(\prod_{i=0}^n(1-x^i)\Big) [[xkj]](i=0n(1xi)) 其实是个 0 / 1 0/1 0/1 背包问题!每个物品的大小依次是 1 1 1 n n n ,要么不取,要么取,取的话贡献的权值不是 1 1 1 而是 − 1 -1 1,背包记录的是所有方案物品权值积的和。也可以说,绝对值是放物品方案数,符号是 − 1 -1 1 的物品个数次方。

这个就很让人头疼。我们知道背包问题是根硬骨头,铁打的 O ( n m ) O(nm) O(nm) ,经常让人绝望。但是也不乏一些特殊情况的优化,比如用随机排序解决总和为 0 0 0 的背包最值问题(缩小背包大小),再比如这道题。这道题的特殊之处就在于每个物品的大小分别是 1 1 1 n n n

这样我们可以发现一个结论:物品个数不超过 k \sqrt k k 的级别!因为考虑最坏情况,只需要 1 1 1~ k \sqrt k k 放进去,总大小就足以达到 k k k 左右。

所以,设 d p [ i ] [ j ] dp[i][j] dp[i][j] 为放 i i i 个数进去,总和为 j j j方案数,那么总状态数大约是 k k k\sqrt k kk ,可以存得下。再借鉴LOJ#6089 小 Y 的背包计数问题的思路,我们可以进行 O ( 1 ) O(1) O(1) 转移:

  • 把已有的 i i i 个数每个加一,可得转移: d p [ i ] [ j − i ] → d p [ i ] [ j ] dp[i][j-i]\rightarrow dp[i][j] dp[i][ji]dp[i][j]
  • 把已有的数每个加一,再往里边放一个 1,可得转移 d p [ i − 1 ] [ j − i ] → d p [ i ] [ j ] dp[i-1][j-i]\rightarrow dp[i][j] dp[i1][ji]dp[i][j]
  • 上述两种转移算进来了不合法情况,即最大的数是一个 n + 1 n+1 n+1 ,因此我们把这些情况去除掉。去掉最大的 n + 1 n+1 n+1 后,方案数等于 d p [ i − 1 ] [ j − n − 1 ] dp[i-1][j-n-1] dp[i1][jn1] ,因此可得转移 ( − d p [ i − 1 ] [ j − n − 1 ] ) → d p [ i ] [ j ] (-dp[i-1][j-n-1])\rightarrow dp[i][j] (dp[i1][jn1])dp[i][j]

时间复杂度 O ( k k ) O(k\sqrt k) O(kk ) ,比多项式exp快得多。

CODE

动规做法

#include<set>
#include<map>
#include<stack>
#include<cmath>
#include<ctime>
#include<queue>
#include<bitset>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define MAXN 100005
#define LL long long
#define DB double
#define ENDL putchar('\n')
#define lowbit(x) (-(x) & (x))
#define FI first
#define SE second
#define SI(x) set<x>::iterator
#define BI bitset<63>
#define eps (1e-9)
#define SQ (450)
LL read() {
	LL f=1,x=0;char s = getchar();
	while(s < '0' || s > '9') {if(s=='-')f = -f;s = getchar();}
	while(s >= '0' && s <= '9') {x=x*10+(s-'0');s = getchar();}
	return f*x;
}
void putpos(LL x) {
	if(!x) return ;
	putpos(x/10); putchar('0'+(x%10));
}
void putnum(LL x) {
	if(!x) putchar('0');
	else if(x < 0) putchar('-'),putpos(-x);
	else putpos(x);
}
const int MOD = 1000000007;
int n,m,s,o,k;
int fac[MAXN<<2],inv[MAXN<<2],invf[MAXN<<2];
int C(int n,int m) {
	if(m < 0 || m > n) return 0;
	return fac[n] *1ll* invf[n-m] % MOD *1ll* invf[m] % MOD;
}
int dp[SQ][MAXN];
int dpp[MAXN];
int main() {
	n = read();k = read();
	int sq = 1;
	while((sq+1)*1ll*(sq+2)/2ll <= k) sq ++;
	fac[0]=fac[1]=inv[0]=inv[1]=invf[0]=invf[1]=1;
	for(int i = 2;i <= 200000;i ++) {
		fac[i] = fac[i-1]*1ll*i % MOD;
		inv[i] = (MOD-inv[MOD%i]) *1ll* (MOD/i) % MOD;
		invf[i] = invf[i-1] *1ll* inv[i] % MOD;
	}
	dp[0][0] = 1;
	dpp[0] = 1;
	for(int i = 1;i <= sq;i ++) {
		for(int j = (i+1)*i/2;j <= k;j ++) {
			dp[i][j] = (dp[i][j-i] + dp[i-1][j-i]) % MOD;
			if(j > n) (dp[i][j] += MOD-dp[i-1][j-n-1]) %= MOD;
			(dpp[j] += ((i&1) ? (MOD-1):1)*1ll*dp[i][j] % MOD) %= MOD;
		}
	}
	int ans = 0;
	for(int i = 0;i <= k;i ++) {
		(ans += C(i+n-1,i) *1ll* dpp[k-i] % MOD) %= MOD;
	}
	printf("%d\n",ans);
	return 0;
}

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值