LOJ #6077. 「2017 山东一轮集训 Day7」逆序对

#6077. 「2017 山东一轮集训 Day7」逆序对

思路

没怎么做过这种通过生成函数来表示转移过程的 DP,比较生疏,参考了很久题解,不过过程很有启发性。

考虑最暴力的 DP,设 \(f_{i,j}\) 表示从小到大正在填第 \(i\) 个数,逆序对个数一共有 \(j\) 的方案数。有转移:

\[f_{i,j}=\sum_{k=0}^{i-1}f_{i-1,j-k} \]

含义是考虑将第 \(i\) 个数放在之前的那个位置上,新产生的逆序对个数是 \([0,i-1]\) 的一个连续段,可以从中的所有状态转移过来。
显然 T 飞。发现这个转移的过程很规整,考虑用生成函数来表示转移的过程。对于不存在的 \(f\),我们将其值视为 \(0\)

\(F_i(x)=\sum_{j=0}^{\infty}f_{i,j}x^j\),也就是一般生成函数。那显然转移为

\[F_i(x)=F_{i-1}(x)\times \sum_{j=0}^{i-1}x^j \]

发现确实比较规整,直接一直卷到 \(n\) 就直接有

\[F_n(x)=\prod_{i=1}^n \sum_{j=0}^{i-1}x^j \]

答案就是其 \(k\) 项系数。考虑和式根本做不了,于是转封闭形式

\[\sum_{j=0}^{i-1}x^j=\frac{1-x^i}{1-x} \]

于是原式可以拆成两部分

\[F_n(x)=(1-x)^{-n}\times \prod_{i=1}^n (1-x^i) \]

左半边的东西显然是好求的,广义二项式定理展开有

\[(1-x)^{-n}=\sum_{j=0}^\infty{-n\choose j}(-x)^k=\sum_{j=0}^\infty (-1)^k {n+k-1\choose k}(-x)^k=\sum_{j=0}^\infty {n+k-1\choose k}x^k \]

后面看起来不是很好直接做。一个比较牛的转化是发现连乘实际上等价于每次选 \(1\) 或者 \(-x^i\),于是问题就转化成了对于一个 \([1,n]\) 的排列,从中选出一些数来,然后和恰好为次数的方案数,这个方案数就是对应次数的系数。

于是我们有一个额外的 DP 去算上面的方案数。设 \(g_{i,j}\) 表示在排列中选了 \(i\) 个数,这些数的和恰好为 \(j\) 的方案数。根据经典套路,我们将其看作全局加 1 或者向里面加数的形式。
具体的,由于我们要求了所有数互不相同,于是我们等价于有两种操作:

  1. 全局加任意次 1。
  2. 全局加一次 1 并且向数列中加入一个新的 1。

注意到这一定可以不重不漏地遍历每一种方案。但是这道题特殊在于要求选的数的大小不大于 \(n\)。由于我们每次只可能全局加 1,因此只可能出现 \(n+1\),因此我们只需要将出现了 \(n+1\) 的情况减掉即可。也就是钦定当前数列中有一个 \(n+1\),然后其他数正常选的方案数,发现实际上与 \(g_{i-1,j-n-1}\) 等价。

具体的转移即 \(g_{i,j}\gets g_{i,j-i}+g_{i-1,j-i}-[j>n]g_{i-1,j-n-1}\)

但是由于我们每次选的是 \(-x^i\),是带符号的,因此我们要判一下符号。写出来即为

\[\prod_{i=1}^n (1-x^i)=\sum_{i=0}^{n(n+1)/2}\sum_{j=0}^{\sqrt{2i}}(-1)^ig_{j,i}x^i \]

这个上下界还是比较好理解的。就是根据其实际意义发现其不可能超过这个上界的值。

于是答案就是两个东西卷起来。

\[ans=[x^k]F_n(x)=\sum_{i=0}^k {n+i-1\choose i}\sum_{j=0}^{\sqrt{2(k-i)}}(-1)^{k-i}f_{j,k-i} \]

时空复杂度都是 \(O(k\sqrt k)\)

code

没什么细节。实现的时候由于最原始的 DP 已经隐去了,于是将 \(g\) 写作了 \(f\)

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e5+7,M=455,p=1e9+7;
int n,K,pw[N<<1],ni[N<<1],f[M][N];
int ksm(int x,int k){
	int res=1;while(k){
		if(k&1)res=res*x%p;
		x=x*x%p;k>>=1;
	}
	return res;
}
int inv(int x){return ksm(x,p-2);}
int C(int x,int y){if(y>x)return 0;return pw[x]*ni[x-y]%p*ni[y]%p;}
int add(int x,int y){return x+y>=p?x+y-p:(x+y<0?x+y+p:x+y);}
void upd(int &x,int y){x=add(x,y);}
void calc(){
	f[0][0]=1;
	for(int i=1;i<=450;i++){
		for(int j=i;j<=K;j++){
			upd(f[i][j],f[i][j-i]+f[i-1][j-i]);
			if(j>=n+1)upd(f[i][j],-f[i-1][j-n-1]);
		}
	}
}
signed main(){
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin>>n>>K;pw[0]=1;for(int i=1;i<=n+K;i++)pw[i]=pw[i-1]*i%p;
	ni[n+K]=inv(pw[n+K]);for(int i=n+K-1;i>=0;i--)ni[i]=ni[i+1]*(i+1)%p;
	calc();int ans=0;
	for(int i=0;i<=K;i++){
		int tmp=0;
		for(int j=0;j<=sqrt(2*(K-i));j++){
			upd(tmp,((j)&1?-1ll:1ll)*f[j][K-i]);
		}
		upd(ans,tmp*C(n+i-1,i)%p);
	}
	cout<<ans;return 0;
}
posted @ 2025-07-31 12:07  all_for_god  阅读(26)  评论(1)    收藏  举报